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/AssetProxy/MixinERC721Transfer.sol8
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol36
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol38
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol7
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/libs/LibEIP712.sol64
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol4
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol66
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol3
-rw-r--r--packages/contracts/src/contracts/current/test/ExchangeWrapper/ExchangeWrapper.sol98
-rw-r--r--packages/contracts/src/contracts/current/test/TestLibs/TestLibs.sol4
-rw-r--r--packages/contracts/src/contracts/current/test/Whitelist/Whitelist.sol2
-rw-r--r--packages/contracts/src/contracts/current/utils/LibMem/LibMem.sol22
-rw-r--r--packages/contracts/src/utils/artifacts.ts2
-rw-r--r--packages/contracts/src/utils/order_factory.ts2
-rw-r--r--packages/contracts/src/utils/transaction_factory.ts27
-rw-r--r--packages/contracts/test/asset_proxy/proxies.ts8
-rw-r--r--packages/contracts/test/exchange/core.ts30
-rw-r--r--packages/contracts/test/exchange/libs.ts10
-rw-r--r--packages/contracts/test/exchange/transactions.ts112
21 files changed, 444 insertions, 103 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/AssetProxy/MixinERC721Transfer.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol
index d09aba599..9dc9e6525 100644
--- a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol
@@ -53,13 +53,7 @@ contract MixinERC721Transfer is
bytes memory receiverData
) = decodeERC721AssetData(assetData);
- // Transfer token. Saves gas by calling safeTransferFrom only
- // when there is receiverData present. Either succeeds or throws.
- if (receiverData.length > 0) {
- ERC721Token(token).safeTransferFrom(from, to, tokenId, receiverData);
- } else {
- ERC721Token(token).transferFrom(from, to, tokenId);
- }
+ ERC721Token(token).safeTransferFrom(from, to, tokenId, receiverData);
}
/// @dev Decodes ERC721 Asset data.
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol
index 0a0f0209a..c406354a7 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol
@@ -44,32 +44,36 @@ 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
- // Orders with a salt less than their maker's epoch are considered cancelled
- mapping (address => uint256) public makerEpoch;
+ // Mapping of makerAddress => senderAddress => lowest salt an order can have in order to be fillable
+ // Orders with specified senderAddress and with a salt less than their epoch to are considered cancelled
+ mapping (address => mapping (address => uint256)) public orderEpoch;
////// Core exchange functions //////
- /// @dev Cancels all orders created by sender with a salt less than or equal to the specified salt value.
- /// @param salt Orders created with a salt less or equal to this value will be cancelled.
- function cancelOrdersUpTo(uint256 salt)
+ /// @dev Cancels all orders created by makerAddress with a salt less than or equal to the targetOrderEpoch
+ /// and senderAddress equal to msg.sender (or null address if msg.sender == makerAddress).
+ /// @param targetOrderEpoch Orders created with a salt less or equal to this value will be cancelled.
+ function cancelOrdersUpTo(uint256 targetOrderEpoch)
external
{
address makerAddress = getCurrentContextAddress();
+ // If this function is called via `executeTransaction`, we only update the orderEpoch 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];
+ // orderEpoch is initialized to 0, so to cancelUpTo we need salt + 1
+ uint256 newOrderEpoch = targetOrderEpoch + 1;
+ uint256 oldOrderEpoch = orderEpoch[makerAddress][senderAddress];
- // Ensure makerEpoch is monotonically increasing
+ // Ensure orderEpoch is monotonically increasing
require(
- newMakerEpoch > oldMakerEpoch,
- INVALID_NEW_MAKER_EPOCH
+ newOrderEpoch > oldOrderEpoch,
+ INVALID_NEW_ORDER_EPOCH
);
- // Update makerEpoch
- makerEpoch[makerAddress] = newMakerEpoch;
- emit CancelUpTo(makerAddress, newMakerEpoch);
+ // Update orderEpoch
+ orderEpoch[makerAddress][senderAddress] = newOrderEpoch;
+ emit CancelUpTo(makerAddress, senderAddress, newOrderEpoch);
}
/// @dev Fills the input order.
@@ -180,7 +184,7 @@ contract MixinExchangeCore is
orderInfo.orderStatus = uint8(OrderStatus.CANCELLED);
return orderInfo;
}
- if (makerEpoch[order.makerAddress] > order.salt) {
+ if (orderEpoch[order.makerAddress][order.senderAddress] > order.salt) {
orderInfo.orderStatus = uint8(OrderStatus.CANCELLED);
return orderInfo;
}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol
index 30b0102fd..f1332363c 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol
@@ -20,8 +20,11 @@ pragma solidity ^0.4.24;
import "./libs/LibExchangeErrors.sol";
import "./mixins/MSignatureValidator.sol";
import "./mixins/MTransactions.sol";
+import "./libs/LibExchangeErrors.sol";
+import "./libs/LibEIP712.sol";
contract MixinTransactions is
+ LibEIP712,
LibExchangeErrors,
MSignatureValidator,
MTransactions
@@ -34,6 +37,33 @@ contract MixinTransactions is
// Address of current transaction signer
address public currentContextAddress;
+ // Hash for the EIP712 ZeroEx Transaction Schema
+ bytes32 constant EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH = keccak256(abi.encodePacked(
+ "ZeroExTransaction(",
+ "uint256 salt,",
+ "address signer,",
+ "bytes data",
+ ")"
+ ));
+
+ /// @dev Calculates EIP712 hash of the Transaction.
+ /// @param salt Arbitrary number to ensure uniqueness of transaction hash.
+ /// @param signer Address of transaction signer.
+ /// @param data AbiV2 encoded calldata.
+ /// @return EIP712 hash of the Transaction.
+ function hashZeroExTransaction(uint256 salt, address signer, bytes data)
+ internal
+ pure
+ returns (bytes32)
+ {
+ return keccak256(abi.encode(
+ EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH,
+ salt,
+ signer,
+ keccak256(data)
+ ));
+ }
+
/// @dev Executes an exchange method call in the context of signer.
/// @param salt Arbitrary number to ensure uniqueness of transaction hash.
/// @param signer Address of transaction signer.
@@ -53,13 +83,7 @@ contract MixinTransactions is
REENTRANCY_ILLEGAL
);
- // Calculate transaction hash
- bytes32 transactionHash = keccak256(abi.encodePacked(
- address(this),
- signer,
- salt,
- data
- ));
+ bytes32 transactionHash = hashEIP712Message(hashZeroExTransaction(salt, signer, data));
// Validate transaction has not been executed
require(
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol
index 7ca2dd052..98222f33f 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol
@@ -24,9 +24,10 @@ import "../libs/LibFillResults.sol";
contract IExchangeCore {
- /// @dev Cancels all orders reated by sender with a salt less than or equal to the specified salt value.
- /// @param salt Orders created with a salt less or equal to this value will be cancelled.
- function cancelOrdersUpTo(uint256 salt)
+ /// @dev Cancels all orders created by makerAddress with a salt less than or equal to the targetOrderEpoch
+ /// and senderAddress equal to msg.sender (or null address if msg.sender == makerAddress).
+ /// @param targetOrderEpoch Orders created with a salt less or equal to this value will be cancelled.
+ function cancelOrdersUpTo(uint256 targetOrderEpoch)
external;
/// @dev Fills the input order.
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibEIP712.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibEIP712.sol
new file mode 100644
index 000000000..b983347a4
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibEIP712.sol
@@ -0,0 +1,64 @@
+/*
+
+ 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;
+
+contract LibEIP712 {
+ // EIP191 header for EIP712 prefix
+ string constant EIP191_HEADER = "\x19\x01";
+
+ // EIP712 Domain Name value
+ string constant EIP712_DOMAIN_NAME = "0x Protocol";
+
+ // EIP712 Domain Version value
+ string constant EIP712_DOMAIN_VERSION = "2";
+
+ // Hash of the EIP712 Domain Separator Schema
+ bytes32 public constant EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH = keccak256(abi.encodePacked(
+ "EIP712Domain(",
+ "string name,",
+ "string version,",
+ "address verifyingContract",
+ ")"
+ ));
+
+ // Hash of the EIP712 Domain Separator data
+ bytes32 public EIP712_DOMAIN_HASH;
+
+ constructor ()
+ public
+ {
+ EIP712_DOMAIN_HASH = keccak256(abi.encode(
+ EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH,
+ keccak256(bytes(EIP712_DOMAIN_NAME)),
+ keccak256(bytes(EIP712_DOMAIN_VERSION)),
+ address(this)
+ ));
+ }
+
+ /// @dev Calculates EIP712 encoding for a hash struct in this EIP712 Domain.
+ /// @param hashStruct The EIP712 hash struct.
+ /// @return EIP712 hash applied to this EIP712 Domain.
+ function hashEIP712Message(bytes32 hashStruct)
+ internal
+ view
+ returns (bytes32)
+ {
+ return keccak256(abi.encodePacked(EIP191_HEADER, EIP712_DOMAIN_HASH, hashStruct));
+ }
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol
index 48dd0f8be..adf27bec3 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol
@@ -36,7 +36,7 @@ contract LibExchangeErrors {
string constant SIGNATURE_UNSUPPORTED = "SIGNATURE_UNSUPPORTED"; // Signature type unsupported.
/// cancelOrdersUptTo errors ///
- string constant INVALID_NEW_MAKER_EPOCH = "INVALID_NEW_MAKER_EPOCH"; // Specified salt must be greater than or equal to existing makerEpoch.
+ string constant INVALID_NEW_ORDER_EPOCH = "INVALID_NEW_ORDER_EPOCH"; // Specified salt must be greater than or equal to existing orderEpoch.
/// fillOrKillOrder errors ///
string constant COMPLETE_FILL_FAILED = "COMPLETE_FILL_FAILED"; // Desired takerAssetFillAmount could not be completely filled.
@@ -55,7 +55,7 @@ contract LibExchangeErrors {
string constant ASSET_PROXY_ID_MISMATCH = "ASSET_PROXY_ID_MISMATCH"; // newAssetProxyId does not match given assetProxyId.
/// Length validation errors ///
- string constant LENGTH_GREATER_THAN_0_REQUIRED = "LENGTH_GREATER_THAN_0_REQUIRED"; // Byte array must have a length greater than 0.
+ string constant LENGTH_GREATER_THAN_0_REQUIRED = "LENGTH_GREATER_THAN_0_REQUIRED"; // Byte array must have a length greater than 0.
string constant LENGTH_0_REQUIRED = "LENGTH_1_REQUIRED"; // Byte array must have a length of 1.
string constant LENGTH_65_REQUIRED = "LENGTH_66_REQUIRED"; // Byte array must have a length of 66.
}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol
index 05ea27ffc..bfc7aaae0 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol
@@ -18,13 +18,14 @@
pragma solidity ^0.4.24;
-contract LibOrder {
+import "./LibEIP712.sol";
- bytes32 constant DOMAIN_SEPARATOR_SCHEMA_HASH = keccak256(abi.encodePacked(
- "DomainSeparator(address contract)"
- ));
+contract LibOrder is
+ LibEIP712
+{
- bytes32 constant ORDER_SCHEMA_HASH = keccak256(abi.encodePacked(
+ // Hash for the EIP712 Order Schema
+ bytes32 constant EIP712_ORDER_SCHEMA_HASH = keccak256(abi.encodePacked(
"Order(",
"address makerAddress,",
"address takerAddress,",
@@ -37,7 +38,7 @@ contract LibOrder {
"uint256 expirationTimeSeconds,",
"uint256 salt,",
"bytes makerAssetData,",
- "bytes takerAssetData,",
+ "bytes takerAssetData",
")"
));
@@ -85,27 +86,38 @@ contract LibOrder {
view
returns (bytes32 orderHash)
{
- // TODO: EIP712 is not finalized yet
- // Source: https://github.com/ethereum/EIPs/pull/712
- orderHash = keccak256(abi.encodePacked(
- DOMAIN_SEPARATOR_SCHEMA_HASH,
- keccak256(abi.encodePacked(address(this))),
- ORDER_SCHEMA_HASH,
- keccak256(abi.encodePacked(
- order.makerAddress,
- order.takerAddress,
- order.feeRecipientAddress,
- order.senderAddress,
- order.makerAssetAmount,
- order.takerAssetAmount,
- order.makerFee,
- order.takerFee,
- order.expirationTimeSeconds,
- order.salt,
- keccak256(abi.encodePacked(order.makerAssetData)),
- keccak256(abi.encodePacked(order.takerAssetData))
- ))
- ));
+ orderHash = hashEIP712Message(hashOrder(order));
return orderHash;
}
+
+ /// @dev Calculates EIP712 hash of the order.
+ /// @param order The order structure.
+ /// @return EIP712 hash of the order.
+ function hashOrder(Order memory order)
+ internal
+ pure
+ returns (bytes32 result)
+ {
+ bytes32 schemaHash = EIP712_ORDER_SCHEMA_HASH;
+ bytes32 makerAssetDataHash = keccak256(order.makerAssetData);
+ bytes32 takerAssetDataHash = keccak256(order.takerAssetData);
+ assembly {
+ // Backup
+ let temp1 := mload(sub(order, 32))
+ let temp2 := mload(add(order, 320))
+ let temp3 := mload(add(order, 352))
+
+ // Hash in place
+ mstore(sub(order, 32), schemaHash)
+ mstore(add(order, 320), makerAssetDataHash)
+ mstore(add(order, 352), takerAssetDataHash)
+ result := keccak256(sub(order, 32), 416)
+
+ // Restore
+ mstore(sub(order, 32), temp1)
+ mstore(add(order, 320), temp2)
+ mstore(add(order, 352), temp3)
+ }
+ return result;
+ }
}
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..fb345294c 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol
@@ -52,7 +52,8 @@ contract MExchangeCore is
// CancelUpTo event is emitted whenever `cancelOrdersUpTo` is executed succesfully.
event CancelUpTo(
address indexed makerAddress,
- uint256 makerEpoch
+ address indexed senderAddress,
+ uint256 orderEpoch
);
/// @dev Updates state with results of a fill order.
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..5baaf6e5a
--- /dev/null
+++ b/packages/contracts/src/contracts/current/test/ExchangeWrapper/ExchangeWrapper.sol
@@ -0,0 +1,98 @@
+/*
+
+ 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 targetOrderEpoch
+ /// and senderAddress equal to this contract.
+ /// @param targetOrderEpoch 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 targetOrderEpoch,
+ uint256 salt,
+ bytes makerSignature
+ )
+ external
+ {
+ address makerAddress = msg.sender;
+
+ // Encode arguments into byte array.
+ bytes memory data = abi.encodeWithSelector(
+ EXCHANGE.cancelOrdersUpTo.selector,
+ targetOrderEpoch
+ );
+
+ // Call `cancelOrdersUpTo` via `executeTransaction`.
+ EXCHANGE.executeTransaction(
+ salt,
+ makerAddress,
+ data,
+ makerSignature
+ );
+ }
+}
diff --git a/packages/contracts/src/contracts/current/test/TestLibs/TestLibs.sol b/packages/contracts/src/contracts/current/test/TestLibs/TestLibs.sol
index 47ce0dcf3..010080703 100644
--- a/packages/contracts/src/contracts/current/test/TestLibs/TestLibs.sol
+++ b/packages/contracts/src/contracts/current/test/TestLibs/TestLibs.sol
@@ -74,7 +74,7 @@ contract TestLibs is
pure
returns (bytes32)
{
- return ORDER_SCHEMA_HASH;
+ return EIP712_ORDER_SCHEMA_HASH;
}
function getDomainSeparatorSchemaHash()
@@ -82,7 +82,7 @@ contract TestLibs is
pure
returns (bytes32)
{
- return DOMAIN_SEPARATOR_SCHEMA_HASH;
+ return EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH;
}
function publicAddFillResults(FillResults memory totalFillResults, FillResults memory singleFillResults)
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/contracts/current/utils/LibMem/LibMem.sol b/packages/contracts/src/contracts/current/utils/LibMem/LibMem.sol
index 6afb9973a..97fb5fb0f 100644
--- a/packages/contracts/src/contracts/current/utils/LibMem/LibMem.sol
+++ b/packages/contracts/src/contracts/current/utils/LibMem/LibMem.sol
@@ -80,9 +80,6 @@ contract LibMem
//
if (source > dest) {
assembly {
- // Record the total number of full words to copy
- let nWords := div(length, 32)
-
// We subtract 32 from `sEnd` and `dEnd` because it
// is easier to compare with in the loop, and these
// are also the addresses we need for copying the
@@ -98,20 +95,19 @@ contract LibMem
let last := mload(sEnd)
// Copy whole words front to back
- for {let i := 0} lt(i, nWords) {i := add(i, 1)} {
+ // Note: the first check is always true,
+ // this could have been a do-while loop.
+ for {} lt(source, sEnd) {} {
mstore(dest, mload(source))
source := add(source, 32)
dest := add(dest, 32)
}
-
+
// Write the last 32 bytes
mstore(dEnd, last)
}
} else {
assembly {
- // Record the total number of full words to copy
- let nWords := div(length, 32)
-
// We subtract 32 from `sEnd` and `dEnd` because those
// are the starting points when copying a word at the end.
length := sub(length, 32)
@@ -125,12 +121,18 @@ contract LibMem
let first := mload(source)
// Copy whole words back to front
- for {let i := 0} lt(i, nWords) {i := add(i, 1)} {
+ // We use a signed comparisson here to allow dEnd to become
+ // negative (happens when source and dest < 32). Valid
+ // addresses in local memory will never be larger than
+ // 2**255, so they can be safely re-interpreted as signed.
+ // Note: the first check is always true,
+ // this could have been a do-while loop.
+ for {} slt(dest, dEnd) {} {
mstore(dEnd, mload(sEnd))
sEnd := sub(sEnd, 32)
dEnd := sub(dEnd, 32)
}
-
+
// Write the first 32 bytes
mstore(dest, first)
}
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/src/utils/order_factory.ts b/packages/contracts/src/utils/order_factory.ts
index 6e4c9a883..009dbc396 100644
--- a/packages/contracts/src/utils/order_factory.ts
+++ b/packages/contracts/src/utils/order_factory.ts
@@ -26,7 +26,7 @@ export class OrderFactory {
...this._defaultOrderParams,
...customOrderParams,
} as any) as Order;
- const orderHashBuff = orderHashUtils.getOrderHashBuff(order);
+ const orderHashBuff = orderHashUtils.getOrderHashBuffer(order);
const signature = signingUtils.signMessage(orderHashBuff, this._privateKey, signatureType);
const signedOrder = {
...order,
diff --git a/packages/contracts/src/utils/transaction_factory.ts b/packages/contracts/src/utils/transaction_factory.ts
index a060263b1..19ef4e1bf 100644
--- a/packages/contracts/src/utils/transaction_factory.ts
+++ b/packages/contracts/src/utils/transaction_factory.ts
@@ -1,10 +1,19 @@
-import { crypto, generatePseudoRandomSalt } from '@0xproject/order-utils';
+import { EIP712Schema, EIP712Types, EIP712Utils, generatePseudoRandomSalt } from '@0xproject/order-utils';
import { SignatureType } from '@0xproject/types';
import * as ethUtil from 'ethereumjs-util';
import { signingUtils } from './signing_utils';
import { SignedTransaction } from './types';
+const EIP712_ZEROEX_TRANSACTION_SCHEMA: EIP712Schema = {
+ name: 'ZeroExTransaction',
+ parameters: [
+ { name: 'salt', type: EIP712Types.Uint256 },
+ { name: 'signer', type: EIP712Types.Address },
+ { name: 'data', type: EIP712Types.Bytes },
+ ],
+};
+
export class TransactionFactory {
private _signerBuff: Buffer;
private _exchangeAddress: string;
@@ -16,14 +25,22 @@ export class TransactionFactory {
}
public newSignedTransaction(data: string, signatureType: SignatureType = SignatureType.EthSign): SignedTransaction {
const salt = generatePseudoRandomSalt();
- const txHash = crypto.solSHA3([this._exchangeAddress, this._signerBuff, salt, ethUtil.toBuffer(data)]);
+ const signer = `0x${this._signerBuff.toString('hex')}`;
+ const executeTransactionData = {
+ salt,
+ signer,
+ data,
+ };
+ const executeTransactionHashBuff = EIP712Utils.structHash(
+ EIP712_ZEROEX_TRANSACTION_SCHEMA,
+ executeTransactionData,
+ );
+ const txHash = EIP712Utils.createEIP712Message(executeTransactionHashBuff, this._exchangeAddress);
const signature = signingUtils.signMessage(txHash, this._privateKey, signatureType);
const signedTx = {
exchangeAddress: this._exchangeAddress,
- salt,
- signer: `0x${this._signerBuff.toString('hex')}`,
- data,
signature: `0x${signature.toString('hex')}`,
+ ...executeTransactionData,
};
return signedTx;
}
diff --git a/packages/contracts/test/asset_proxy/proxies.ts b/packages/contracts/test/asset_proxy/proxies.ts
index 185d1639e..7f6d68e1b 100644
--- a/packages/contracts/test/asset_proxy/proxies.ts
+++ b/packages/contracts/test/asset_proxy/proxies.ts
@@ -279,7 +279,7 @@ describe('Asset Transfer Proxies', () => {
expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress);
});
- it('should not call onERC721Received when transferring to a smart contract without receiver data', async () => {
+ it('should call onERC721Received when transferring to a smart contract without receiver data', async () => {
// Construct ERC721 asset data
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
@@ -300,7 +300,11 @@ describe('Asset Transfer Proxies', () => {
const logDecoder = new LogDecoder(web3Wrapper, erc721Receiver.address);
const tx = await logDecoder.getTxWithDecodedLogsAsync(txHash);
// Verify that no log was emitted by erc721 receiver
- expect(tx.logs.length).to.be.equal(0);
+ expect(tx.logs.length).to.be.equal(1);
+ const tokenReceivedLog = tx.logs[0] as LogWithDecodedArgs<TokenReceivedContractEventArgs>;
+ expect(tokenReceivedLog.args.from).to.be.equal(makerAddress);
+ expect(tokenReceivedLog.args.tokenId).to.be.bignumber.equal(erc721MakerTokenId);
+ expect(tokenReceivedLog.args.data).to.be.equal(constants.NULL_BYTES);
// Verify transfer was successful
const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
expect(newOwnerMakerAsset).to.be.bignumber.equal(erc721Receiver.address);
diff --git a/packages/contracts/test/exchange/core.ts b/packages/contracts/test/exchange/core.ts
index 64b7bf943..3c0116beb 100644
--- a/packages/contracts/test/exchange/core.ts
+++ b/packages/contracts/test/exchange/core.ts
@@ -252,30 +252,30 @@ describe('Exchange core', () => {
});
describe('cancelOrdersUpTo', () => {
- it('should fail to set makerEpoch less than current makerEpoch', async () => {
- const makerEpoch = new BigNumber(1);
- await exchangeWrapper.cancelOrdersUpToAsync(makerEpoch, makerAddress);
- const lesserMakerEpoch = new BigNumber(0);
+ it('should fail to set orderEpoch less than current orderEpoch', async () => {
+ const orderEpoch = new BigNumber(1);
+ await exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress);
+ const lesserOrderEpoch = new BigNumber(0);
return expectRevertOrAlwaysFailingTransactionAsync(
- exchangeWrapper.cancelOrdersUpToAsync(lesserMakerEpoch, makerAddress),
+ exchangeWrapper.cancelOrdersUpToAsync(lesserOrderEpoch, makerAddress),
);
});
- it('should fail to set makerEpoch equal to existing makerEpoch', async () => {
- const makerEpoch = new BigNumber(1);
- await exchangeWrapper.cancelOrdersUpToAsync(makerEpoch, makerAddress);
+ it('should fail to set orderEpoch equal to existing orderEpoch', async () => {
+ const orderEpoch = new BigNumber(1);
+ await exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress);
return expectRevertOrAlwaysFailingTransactionAsync(
- exchangeWrapper.cancelOrdersUpToAsync(makerEpoch, makerAddress),
+ exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress),
);
});
- it('should cancel only orders with a makerEpoch less than existing makerEpoch', async () => {
- // Cancel all transactions with a makerEpoch less than 1
- const makerEpoch = new BigNumber(1);
- await exchangeWrapper.cancelOrdersUpToAsync(makerEpoch, makerAddress);
+ it('should cancel only orders with a orderEpoch less than existing orderEpoch', async () => {
+ // Cancel all transactions with a orderEpoch less than 1
+ const orderEpoch = new BigNumber(1);
+ await exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress);
- // Create 3 orders with makerEpoch values: 0,1,2,3
- // Since we cancelled with makerEpoch=1, orders with makerEpoch<=1 will not be processed
+ // Create 3 orders with orderEpoch values: 0,1,2,3
+ // Since we cancelled with orderEpoch=1, orders with orderEpoch<=1 will not be processed
erc20Balances = await erc20Wrapper.getBalancesAsync();
const signedOrders = [
orderFactory.newSignedOrder({
diff --git a/packages/contracts/test/exchange/libs.ts b/packages/contracts/test/exchange/libs.ts
index eff05981d..c08001198 100644
--- a/packages/contracts/test/exchange/libs.ts
+++ b/packages/contracts/test/exchange/libs.ts
@@ -1,5 +1,5 @@
import { BlockchainLifecycle } from '@0xproject/dev-utils';
-import { assetProxyUtils, orderHashUtils } from '@0xproject/order-utils';
+import { assetProxyUtils, EIP712Utils, orderHashUtils } from '@0xproject/order-utils';
import { SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
@@ -56,13 +56,17 @@ describe('Exchange libs', () => {
describe('getOrderSchema', () => {
it('should output the correct order schema hash', async () => {
const orderSchema = await libs.getOrderSchemaHash.callAsync();
- expect(orderHashUtils._getOrderSchemaHex()).to.be.equal(orderSchema);
+ const schemaHashBuffer = orderHashUtils._getOrderSchemaBuffer();
+ const schemaHashHex = `0x${schemaHashBuffer.toString('hex')}`;
+ expect(schemaHashHex).to.be.equal(orderSchema);
});
});
describe('getDomainSeparatorSchema', () => {
it('should output the correct domain separator schema hash', async () => {
const domainSeparatorSchema = await libs.getDomainSeparatorSchemaHash.callAsync();
- expect(orderHashUtils._getDomainSeparatorSchemaHex()).to.be.equal(domainSeparatorSchema);
+ const domainSchemaBuffer = EIP712Utils._getDomainSeparatorSchemaBuffer();
+ const schemaHashHex = `0x${domainSchemaBuffer.toString('hex')}`;
+ expect(schemaHashHex).to.be.equal(domainSeparatorSchema);
});
});
describe('getOrderHash', () => {
diff --git a/packages/contracts/test/exchange/transactions.ts b/packages/contracts/test/exchange/transactions.ts
index beb7054e7..8e7acc04e 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';
@@ -201,6 +202,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 targetOrderEpoch = orderSalt.add(1);
+ const cancelData = exchange.cancelOrdersUpTo.getABIEncodedTransactionData(targetOrderEpoch);
+ const signedCancelTx = makerTransactionFactory.newSignedTransaction(cancelData);
+ await exchangeWrapperContract.cancelOrdersUpTo.sendTransactionAsync(
+ targetOrderEpoch,
+ 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 targetOrderEpoch = orderSalt.add(1);
+ await exchangeWrapper.cancelOrdersUpToAsync(targetOrderEpoch, 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', () => {