aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contracts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/contracts')
-rw-r--r--packages/contracts/compiler.json1
-rw-r--r--packages/contracts/package.json3
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol15
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol1
-rw-r--r--packages/contracts/src/contracts/current/test/ExchangeWrapper/ExchangeWrapper.sol97
-rw-r--r--packages/contracts/src/contracts/current/test/Whitelist/Whitelist.sol2
-rw-r--r--packages/contracts/src/utils/artifacts.ts2
-rw-r--r--packages/contracts/test/exchange/transactions.ts112
8 files changed, 225 insertions, 8 deletions
diff --git a/packages/contracts/compiler.json b/packages/contracts/compiler.json
index 6ef8e6a95..a5cfa8761 100644
--- a/packages/contracts/compiler.json
+++ b/packages/contracts/compiler.json
@@ -26,6 +26,7 @@
"ERC20Proxy",
"ERC721Proxy",
"Exchange",
+ "ExchangeWrapper",
"MixinAuthorizable",
"MultiSigWallet",
"MultiSigWalletWithTimeLock",
diff --git a/packages/contracts/package.json b/packages/contracts/package.json
index 8807c727e..cf3f6f01d 100644
--- a/packages/contracts/package.json
+++ b/packages/contracts/package.json
@@ -33,7 +33,8 @@
"test:circleci": "yarn test"
},
"config": {
- "abis": "../migrations/artifacts/2.0.0/@(AssetProxyOwner|DummyERC20Token|DummyERC721Receiver|DummyERC721Token|ERC20Proxy|ERC721Proxy|Exchange|MixinAuthorizable|MultiSigWallet|MultiSigWalletWithTimeLock|TestAssetDataDecoders|TestAssetProxyDispatcher|TestLibBytes|TestLibMem|TestLibs|TestSignatureValidator|TokenRegistry|Whitelist|WETH9|ZRXToken).json"
+ "abis":
+ "../migrations/artifacts/2.0.0/@(AssetProxyOwner|DummyERC20Token|DummyERC721Receiver|DummyERC721Token|ERC20Proxy|ERC721Proxy|Exchange|ExchangeWrapper|MixinAuthorizable|MultiSigWallet|MultiSigWalletWithTimeLock|TestAssetDataDecoders|TestAssetProxyDispatcher|TestLibBytes|TestLibMem|TestLibs|TestSignatureValidator|TokenRegistry|Whitelist|WETH9|ZRXToken).json"
},
"repository": {
"type": "git",
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol
index 0a0f0209a..2ebbfdabf 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol
@@ -44,9 +44,9 @@ contract MixinExchangeCore is
// Mapping of orderHash => cancelled
mapping (bytes32 => bool) public cancelled;
- // Mapping of makerAddress => lowest salt an order can have in order to be fillable
+ // Mapping of makerAddress => senderAddress => 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;
+ mapping (address => mapping (address => uint256)) public makerEpoch;
////// Core exchange functions //////
@@ -56,10 +56,13 @@ contract MixinExchangeCore is
external
{
address makerAddress = getCurrentContextAddress();
+ // If this function is called via `executeTransaction`, we only update the makerEpoch for the makerAddress/msg.sender combination.
+ // This allows external filter contracts to add rules to how orders are cancelled via this function.
+ address senderAddress = makerAddress == msg.sender ? address(0) : msg.sender;
// makerEpoch is initialized to 0, so to cancelUpTo we need salt + 1
uint256 newMakerEpoch = salt + 1;
- uint256 oldMakerEpoch = makerEpoch[makerAddress];
+ uint256 oldMakerEpoch = makerEpoch[makerAddress][senderAddress];
// Ensure makerEpoch is monotonically increasing
require(
@@ -68,8 +71,8 @@ contract MixinExchangeCore is
);
// Update makerEpoch
- makerEpoch[makerAddress] = newMakerEpoch;
- emit CancelUpTo(makerAddress, newMakerEpoch);
+ makerEpoch[makerAddress][senderAddress] = newMakerEpoch;
+ emit CancelUpTo(makerAddress, senderAddress, newMakerEpoch);
}
/// @dev Fills the input order.
@@ -180,7 +183,7 @@ contract MixinExchangeCore is
orderInfo.orderStatus = uint8(OrderStatus.CANCELLED);
return orderInfo;
}
- if (makerEpoch[order.makerAddress] > order.salt) {
+ if (makerEpoch[order.makerAddress][order.senderAddress] > order.salt) {
orderInfo.orderStatus = uint8(OrderStatus.CANCELLED);
return orderInfo;
}
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 de7c4d3af..b4552b7bf 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol
@@ -52,6 +52,7 @@ contract MExchangeCore is
// CancelUpTo event is emitted whenever `cancelOrdersUpTo` is executed succesfully.
event CancelUpTo(
address indexed makerAddress,
+ address indexed senderAddress,
uint256 makerEpoch
);
diff --git a/packages/contracts/src/contracts/current/test/ExchangeWrapper/ExchangeWrapper.sol b/packages/contracts/src/contracts/current/test/ExchangeWrapper/ExchangeWrapper.sol
new file mode 100644
index 000000000..629a846d9
--- /dev/null
+++ b/packages/contracts/src/contracts/current/test/ExchangeWrapper/ExchangeWrapper.sol
@@ -0,0 +1,97 @@
+/*
+
+ Copyright 2018 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.4.24;
+pragma experimental ABIEncoderV2;
+
+import "../../protocol/Exchange/interfaces/IExchange.sol";
+import "../../protocol/Exchange/libs/LibOrder.sol";
+
+contract ExchangeWrapper {
+
+ // Exchange contract.
+ IExchange EXCHANGE;
+
+ constructor (address _exchange)
+ public
+ {
+ EXCHANGE = IExchange(_exchange);
+ }
+
+ /// @dev Fills an order using `msg.sender` as the taker.
+ /// @param order Order struct containing order specifications.
+ /// @param takerAssetFillAmount Desired amount of takerAsset to sell.
+ /// @param salt Arbitrary value to gaurantee uniqueness of 0x transaction hash.
+ /// @param orderSignature Proof that order has been created by maker.
+ /// @param takerSignature Proof that taker wishes to call this function with given params.
+ function fillOrder(
+ LibOrder.Order memory order,
+ uint256 takerAssetFillAmount,
+ uint256 salt,
+ bytes memory orderSignature,
+ bytes memory takerSignature
+ )
+ public
+ {
+ address takerAddress = msg.sender;
+
+ // Encode arguments into byte array.
+ bytes memory data = abi.encodeWithSelector(
+ EXCHANGE.fillOrder.selector,
+ order,
+ takerAssetFillAmount,
+ orderSignature
+ );
+
+ // Call `fillOrder` via `executeTransaction`.
+ EXCHANGE.executeTransaction(
+ salt,
+ takerAddress,
+ data,
+ takerSignature
+ );
+ }
+
+ /// @dev Cancels all orders created by sender with a salt less than or equal to the specified salt value.
+ /// @param cancelSalt Orders created with a salt less or equal to this value will be cancelled.
+ /// @param salt Arbitrary value to gaurantee uniqueness of 0x transaction hash.
+ /// @param makerSignature Proof that maker wishes to call this function with given params.
+ function cancelOrdersUpTo(
+ uint256 cancelSalt,
+ uint256 salt,
+ bytes makerSignature
+ )
+ external
+ {
+ address makerAddress = msg.sender;
+
+ // Encode arguments into byte array.
+ bytes memory data = abi.encodeWithSelector(
+ EXCHANGE.cancelOrdersUpTo.selector,
+ cancelSalt
+ );
+
+ // Call `cancelOrdersUpTo` via `executeTransaction`.
+ EXCHANGE.executeTransaction(
+ salt,
+ makerAddress,
+ data,
+ makerSignature
+ );
+ }
+}
diff --git a/packages/contracts/src/contracts/current/test/Whitelist/Whitelist.sol b/packages/contracts/src/contracts/current/test/Whitelist/Whitelist.sol
index 0594e2767..460c9ea42 100644
--- a/packages/contracts/src/contracts/current/test/Whitelist/Whitelist.sol
+++ b/packages/contracts/src/contracts/current/test/Whitelist/Whitelist.sol
@@ -16,7 +16,7 @@
*/
-pragma solidity ^0.4.23;
+pragma solidity ^0.4.24;
pragma experimental ABIEncoderV2;
import "../../protocol/Exchange/interfaces/IExchange.sol";
diff --git a/packages/contracts/src/utils/artifacts.ts b/packages/contracts/src/utils/artifacts.ts
index bf7221d6d..7ba467708 100644
--- a/packages/contracts/src/utils/artifacts.ts
+++ b/packages/contracts/src/utils/artifacts.ts
@@ -7,6 +7,7 @@ import * as DummyERC721Token from '../artifacts/DummyERC721Token.json';
import * as ERC20Proxy from '../artifacts/ERC20Proxy.json';
import * as ERC721Proxy from '../artifacts/ERC721Proxy.json';
import * as Exchange from '../artifacts/Exchange.json';
+import * as ExchangeWrapper from '../artifacts/ExchangeWrapper.json';
import * as MixinAuthorizable from '../artifacts/MixinAuthorizable.json';
import * as MultiSigWallet from '../artifacts/MultiSigWallet.json';
import * as MultiSigWalletWithTimeLock from '../artifacts/MultiSigWalletWithTimeLock.json';
@@ -29,6 +30,7 @@ export const artifacts = {
ERC20Proxy: (ERC20Proxy as any) as ContractArtifact,
ERC721Proxy: (ERC721Proxy as any) as ContractArtifact,
Exchange: (Exchange as any) as ContractArtifact,
+ ExchangeWrapper: (ExchangeWrapper as any) as ContractArtifact,
EtherToken: (EtherToken as any) as ContractArtifact,
MixinAuthorizable: (MixinAuthorizable as any) as ContractArtifact,
MultiSigWallet: (MultiSigWallet as any) as ContractArtifact,
diff --git a/packages/contracts/test/exchange/transactions.ts b/packages/contracts/test/exchange/transactions.ts
index 21ff123e5..088b537cc 100644
--- a/packages/contracts/test/exchange/transactions.ts
+++ b/packages/contracts/test/exchange/transactions.ts
@@ -7,6 +7,7 @@ import * as chai from 'chai';
import { DummyERC20TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c20_token';
import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c20_proxy';
import { ExchangeContract } from '../../src/generated_contract_wrappers/exchange';
+import { ExchangeWrapperContract } from '../../src/generated_contract_wrappers/exchange_wrapper';
import { WhitelistContract } from '../../src/generated_contract_wrappers/whitelist';
import { artifacts } from '../../src/utils/artifacts';
import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions';
@@ -198,6 +199,117 @@ describe('Exchange transactions', () => {
);
});
});
+
+ describe('cancelOrdersUpTo', () => {
+ let exchangeWrapperContract: ExchangeWrapperContract;
+
+ before(async () => {
+ exchangeWrapperContract = await ExchangeWrapperContract.deployFrom0xArtifactAsync(
+ artifacts.ExchangeWrapper,
+ provider,
+ txDefaults,
+ exchange.address,
+ );
+ });
+
+ it("should cancel an order if called from the order's sender", async () => {
+ const orderSalt = new BigNumber(0);
+ signedOrder = orderFactory.newSignedOrder({
+ senderAddress: exchangeWrapperContract.address,
+ salt: orderSalt,
+ });
+ const cancelSalt = orderSalt.add(1);
+ const cancelData = exchange.cancelOrdersUpTo.getABIEncodedTransactionData(cancelSalt);
+ const signedCancelTx = makerTransactionFactory.newSignedTransaction(cancelData);
+ await exchangeWrapperContract.cancelOrdersUpTo.sendTransactionAsync(
+ cancelSalt,
+ signedCancelTx.salt,
+ signedCancelTx.signature,
+ {
+ from: makerAddress,
+ },
+ );
+
+ const takerAssetFillAmount = signedOrder.takerAssetAmount;
+ orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder);
+ const fillData = exchange.fillOrder.getABIEncodedTransactionData(
+ orderWithoutExchangeAddress,
+ takerAssetFillAmount,
+ signedOrder.signature,
+ );
+ const signedFillTx = takerTransactionFactory.newSignedTransaction(fillData);
+ return expectRevertOrAlwaysFailingTransactionAsync(
+ exchangeWrapperContract.fillOrder.sendTransactionAsync(
+ orderWithoutExchangeAddress,
+ takerAssetFillAmount,
+ signedFillTx.salt,
+ signedOrder.signature,
+ signedFillTx.signature,
+ { from: takerAddress },
+ ),
+ );
+ });
+
+ it("should not cancel an order if not called from the order's sender", async () => {
+ const orderSalt = new BigNumber(0);
+ signedOrder = orderFactory.newSignedOrder({
+ senderAddress: exchangeWrapperContract.address,
+ salt: orderSalt,
+ });
+ const cancelSalt = orderSalt.add(1);
+ await exchangeWrapper.cancelOrdersUpToAsync(cancelSalt, makerAddress);
+
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ const takerAssetFillAmount = signedOrder.takerAssetAmount;
+ orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder);
+ const data = exchange.fillOrder.getABIEncodedTransactionData(
+ orderWithoutExchangeAddress,
+ takerAssetFillAmount,
+ signedOrder.signature,
+ );
+ signedTx = takerTransactionFactory.newSignedTransaction(data);
+ await exchangeWrapperContract.fillOrder.sendTransactionAsync(
+ orderWithoutExchangeAddress,
+ takerAssetFillAmount,
+ signedTx.salt,
+ signedOrder.signature,
+ signedTx.signature,
+ { from: takerAddress },
+ );
+
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerAssetFillAmount = takerAssetFillAmount
+ .times(signedOrder.makerAssetAmount)
+ .dividedToIntegerBy(signedOrder.takerAssetAmount);
+ const makerFeePaid = signedOrder.makerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
+ const takerFeePaid = signedOrder.takerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
+ expect(newBalances[makerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerTokenAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultTakerTokenAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid),
+ );
+ expect(newBalances[takerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultTakerTokenAddress].minus(takerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerTokenAddress].add(makerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)),
+ );
+ });
+ });
});
describe('Whitelist', () => {