aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGreg Hysen <greg.hysen@gmail.com>2018-03-16 04:58:54 +0800
committerAmir Bandeali <abandeali1@gmail.com>2018-04-21 04:56:17 +0800
commitaeb0742434471c0a4cb28bebf569b0d4af6b4485 (patch)
tree9921cd5df2b80041a0655948961ddb95743e82cc
parente98b4ea26d90d5d356972fcf038eeef50ebd31f3 (diff)
downloaddexon-sol-tools-aeb0742434471c0a4cb28bebf569b0d4af6b4485.tar
dexon-sol-tools-aeb0742434471c0a4cb28bebf569b0d4af6b4485.tar.gz
dexon-sol-tools-aeb0742434471c0a4cb28bebf569b0d4af6b4485.tar.bz2
dexon-sol-tools-aeb0742434471c0a4cb28bebf569b0d4af6b4485.tar.lz
dexon-sol-tools-aeb0742434471c0a4cb28bebf569b0d4af6b4485.tar.xz
dexon-sol-tools-aeb0742434471c0a4cb28bebf569b0d4af6b4485.tar.zst
dexon-sol-tools-aeb0742434471c0a4cb28bebf569b0d4af6b4485.zip
Implementation and tests in the protocol for cancelling all orders up to a specific time
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol2
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/IExchange.sol34
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol28
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol3
-rw-r--r--packages/contracts/src/utils/exchange_wrapper.ts12
-rw-r--r--packages/contracts/src/utils/types.ts4
-rw-r--r--packages/contracts/test/exchange/core.ts67
7 files changed, 134 insertions, 16 deletions
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol b/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol
index e925634df..7a705a0ee 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol
@@ -30,7 +30,7 @@ contract Exchange is
MixinSettlementProxy,
MixinWrapperFunctions
{
- string constant public VERSION = "2.0.0-alpha";
+ string constant public VERSION = "2.0.1-alpha";
function Exchange(
IToken _zrxToken,
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/IExchange.sol b/packages/contracts/src/contracts/current/protocol/Exchange/IExchange.sol
index 8f17f9315..3a43fca9b 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/IExchange.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/IExchange.sol
@@ -53,38 +53,43 @@ contract IExchange {
uint256 takerTokenCancelledAmount,
bytes32 indexed orderHash
);
-
+
+ event LogCancelBefore(
+ address indexed maker,
+ uint256 salt
+ );
+
function ZRX_TOKEN_CONTRACT()
public view
returns (address);
-
+
function TOKEN_TRANSFER_PROXY_CONTRACT()
public view
returns (address);
-
+
function EXTERNAL_QUERY_GAS_LIMIT()
public view
returns (uint16);
-
+
function VERSION()
public view
returns (string);
-
+
function filled(bytes32)
public view
returns (uint256);
-
+
function cancelled(bytes32)
public view
returns (uint256);
-
+
/// @dev Calculates the sum of values already filled and cancelled for a given order.
/// @param orderHash The Keccak-256 hash of the given order.
/// @return Sum of values already filled and cancelled.
function getUnavailableTakerTokenAmount(bytes32 orderHash)
public view
returns (uint256 unavailableTakerTokenAmount);
-
+
/// @dev Calculates partial value given a numerator and denominator.
/// @param numerator Numerator.
/// @param denominator Denominator.
@@ -93,7 +98,7 @@ contract IExchange {
function getPartialAmount(uint256 numerator, uint256 denominator, uint256 target)
public pure
returns (uint256 partialAmount);
-
+
/// @dev Checks if rounding error > 0.1%.
/// @param numerator Numerator.
/// @param denominator Denominator.
@@ -102,7 +107,7 @@ contract IExchange {
function isRoundingError(uint256 numerator, uint256 denominator, uint256 target)
public pure
returns (bool isError);
-
+
/// @dev Calculates Keccak-256 hash of order with specified parameters.
/// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
/// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
@@ -110,7 +115,7 @@ contract IExchange {
function getOrderHash(address[5] orderAddresses, uint256[6] orderValues)
public view
returns (bytes32 orderHash);
-
+
/// @dev Verifies that an order signature is valid.
/// @param signer address of signer.
/// @param hash Signed Keccak-256 hash.
@@ -126,7 +131,7 @@ contract IExchange {
bytes32 s)
public pure
returns (bool isValid);
-
+
/// @dev Fills the input order.
/// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
/// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
@@ -144,7 +149,7 @@ contract IExchange {
bytes32 s)
public
returns (uint256 takerTokenFilledAmount);
-
+
/// @dev Cancels the input order.
/// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
/// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
@@ -157,6 +162,9 @@ contract IExchange {
public
returns (uint256 takerTokenCancelledAmount);
+ /// @dev Cancels all orders for a specified maker up to a certain time.
+ /// @param salt Orders created with a lower salt value will be cancelled
+ function cancelOrdersBefore(uint256 salt) external;
/// @dev Fills an order with specified parameters and ECDSA signature. Throws if specified amount not filled entirely.
/// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol
index 1f3108188..6d69b1787 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol
@@ -43,6 +43,10 @@ contract MixinExchangeCore is
mapping (bytes32 => uint256) public filled;
mapping (bytes32 => uint256) public cancelled;
+ // Mapping of makerAddress => lowest salt an order can have in order to be fillable
+ // Orders with a salt less than their maker's epoch are considered cancelled
+ mapping (address => uint256) public makerEpoch;
+
event LogFill(
address indexed makerAddress,
address takerAddress,
@@ -66,6 +70,11 @@ contract MixinExchangeCore is
bytes32 indexed orderHash
);
+ event LogCancelBefore(
+ address indexed maker,
+ uint256 salt
+ );
+
/*
* Core exchange functions
*/
@@ -119,6 +128,12 @@ contract MixinExchangeCore is
return 0;
}
+ // Validate order is not cancelled
+ if (order.salt < makerEpoch[order.makerAddress]) {
+ LogError(uint8(Errors.ORDER_FULLY_FILLED_OR_CANCELLED), orderHash);
+ return 0;
+ }
+
// Update state
filled[orderHash] = safeAdd(filled[orderHash], takerTokenFilledAmount);
@@ -154,7 +169,7 @@ contract MixinExchangeCore is
{
// Compute the order hash
bytes32 orderHash = getOrderHash(order);
-
+
// Validate the order
require(order.makerTokenAmount > 0);
require(order.takerTokenAmount > 0);
@@ -186,7 +201,16 @@ contract MixinExchangeCore is
);
return takerTokenCancelledAmount;
}
-
+
+ /// @param salt Orders created with a salt less than this value will be cancelled.
+ function cancelOrdersBefore(uint256 salt)
+ external
+ {
+ require(salt > makerEpoch[msg.sender]); // epoch must be monotonically increasing
+ makerEpoch[msg.sender] = salt;
+ LogCancelBefore(msg.sender, salt);
+ }
+
/// @dev Checks if rounding error > 0.1%.
/// @param numerator Numerator.
/// @param denominator Denominator.
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol
index e40120d01..b39996995 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol
@@ -36,4 +36,7 @@ contract MExchangeCore is LibOrder {
public
returns (uint256 takerTokenCancelledAmount);
+ function cancelOrdersBefore(
+ uint256 salt)
+ external;
}
diff --git a/packages/contracts/src/utils/exchange_wrapper.ts b/packages/contracts/src/utils/exchange_wrapper.ts
index 5996867cb..d4206adc3 100644
--- a/packages/contracts/src/utils/exchange_wrapper.ts
+++ b/packages/contracts/src/utils/exchange_wrapper.ts
@@ -167,6 +167,18 @@ export class ExchangeWrapper {
const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash);
return tx;
}
+ public async cancelOrdersBeforeAsync(
+ timestamp: BigNumber,
+ from: string,
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const txHash = await this._exchange.cancelOrdersBefore.sendTransactionAsync(
+ timestamp,
+ { from },
+ );
+ const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash);
+ return tx;
+ }
+
public async getOrderHashAsync(signedOrder: SignedOrder): Promise<string> {
const order = orderUtils.getOrderStruct(signedOrder);
const orderHash = await this._exchange.getOrderHash.callAsync(order);
diff --git a/packages/contracts/src/utils/types.ts b/packages/contracts/src/utils/types.ts
index 9f874d9ec..1a924a66d 100644
--- a/packages/contracts/src/utils/types.ts
+++ b/packages/contracts/src/utils/types.ts
@@ -28,6 +28,10 @@ export interface BatchCancelOrders {
takerTokenCancelAmounts: BigNumber[];
}
+export interface CancelOrdersBefore {
+ timestamp: BigNumber;
+}
+
export interface DefaultOrderParams {
exchangeAddress: string;
makerAddress: string;
diff --git a/packages/contracts/test/exchange/core.ts b/packages/contracts/test/exchange/core.ts
index 1612ecdcc..3b787953b 100644
--- a/packages/contracts/test/exchange/core.ts
+++ b/packages/contracts/test/exchange/core.ts
@@ -741,4 +741,71 @@ describe('Exchange', () => {
expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_EXPIRED);
});
});
+
+ describe('cancelOrdersBefore', () => {
+ it('should fail to set timestamp less than existing CancelBefore timestamp', async () => {
+ const timestamp = new BigNumber(1);
+ await exWrapper.cancelOrdersBeforeAsync(timestamp, makerAddress);
+ const lesser_timestamp = new BigNumber(0);
+ return expect(
+ exWrapper.cancelOrdersBeforeAsync(lesser_timestamp, makerAddress),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should fail to set timestamp equal to existing CancelBefore timestamp', async () => {
+ const timestamp = new BigNumber(1);
+ await exWrapper.cancelOrdersBeforeAsync(timestamp, makerAddress);
+ return expect(
+ exWrapper.cancelOrdersBeforeAsync(timestamp, makerAddress),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should cancel only orders with a timestamp less than CancelBefore timestamp', async () => {
+ // Cancel all transactions with a timestamp less than 1
+ const timestamp = new BigNumber(1);
+ await exWrapper.cancelOrdersBeforeAsync(timestamp, makerAddress);
+
+ // Create 3 orders with timestamps 0,1,2
+ // Since we cancelled with timestamp=1, orders with timestamp<1 will not be processed
+ balances = await dmyBalances.getAsync();
+ const signedOrders = await Promise.all([
+ orderFactory.newSignedOrder({
+ makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(17), 18),
+ takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(17), 18),
+ salt: new BigNumber(0)}),
+ orderFactory.newSignedOrder({
+ makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(97), 18),
+ takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(97), 18),
+ salt: new BigNumber(1)}),
+ orderFactory.newSignedOrder({
+ makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(979), 18),
+ takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(979), 18),
+ salt: new BigNumber(2)}),
+ ]);
+ await exWrapper.batchFillOrdersNoThrowAsync(signedOrders, takerAddress);
+
+ const newBalances = await dmyBalances.getAsync();
+ const fillMakerTokenAmount = signedOrders[1].makerTokenAmount.add(signedOrders[2].makerTokenAmount);
+ const fillTakerTokenAmount = signedOrders[1].takerTokenAmount.add(signedOrders[2].takerTokenAmount);
+ const makerFeeAmount = signedOrders[1].makerFeeAmount.add(signedOrders[2].makerFeeAmount);
+ const takerFeeAmount = signedOrders[1].takerFeeAmount.add(signedOrders[2].takerFeeAmount);
+ expect(newBalances[makerAddress][signedOrders[2].makerTokenAddress]).to.be.bignumber.equal(
+ balances[makerAddress][signedOrders[2].makerTokenAddress].minus(fillMakerTokenAmount),
+ );
+ expect(newBalances[makerAddress][signedOrders[2].takerTokenAddress]).to.be.bignumber.equal(
+ balances[makerAddress][signedOrders[2].takerTokenAddress].add(fillTakerTokenAmount),
+ );
+ expect(newBalances[makerAddress][zrx.address]).to.be.bignumber.equal(balances[makerAddress][zrx.address].minus(makerFeeAmount));
+ expect(newBalances[takerAddress][signedOrders[2].takerTokenAddress]).to.be.bignumber.equal(
+ balances[takerAddress][signedOrders[2].takerTokenAddress].minus(fillTakerTokenAmount),
+ );
+ expect(newBalances[takerAddress][signedOrders[2].makerTokenAddress]).to.be.bignumber.equal(
+ balances[takerAddress][signedOrders[2].makerTokenAddress].add(fillMakerTokenAmount),
+ );
+ expect(newBalances[takerAddress][zrx.address]).to.be.bignumber.equal(balances[takerAddress][zrx.address].minus(takerFeeAmount));
+ expect(newBalances[feeRecipientAddress][zrx.address]).to.be.bignumber.equal(
+ balances[feeRecipientAddress][zrx.address].add(makerFeeAmount.add(takerFeeAmount)),
+ );
+ });
+ });
}); // tslint:disable-line:max-file-line-count