diff --git a/contracts/tec/CHANGELOG.json b/contracts/tec/CHANGELOG.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/contracts/tec/CHANGELOG.json @@ -0,0 +1 @@ +[] diff --git a/contracts/tec/CHANGELOG.md b/contracts/tec/CHANGELOG.md new file mode 100644 index 0000000000..94ffdfb67b --- /dev/null +++ b/contracts/tec/CHANGELOG.md @@ -0,0 +1,32 @@ + + +CHANGELOG + +## v1.2.3 - _January 17, 2019_ + + * Dependencies updated + +## v1.2.2 - _January 15, 2019_ + + * Dependencies updated + +## v1.2.1 - _January 11, 2019_ + + * Dependencies updated + +## v1.2.0 - _January 9, 2019_ + + * Added Dutch Auction Wrapper (#1465) + +## v1.1.0 - _Invalid date_ + + * Added Balance Threshold Filter (#1383) + * Add OrderMatcher (#1117) + * Add OrderValidator (#1464) + +## v1.0.2 - _December 13, 2018_ + + * Dependencies updated diff --git a/contracts/tec/DEPLOYS.json b/contracts/tec/DEPLOYS.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/contracts/tec/DEPLOYS.json @@ -0,0 +1 @@ +[] diff --git a/contracts/tec/README.md b/contracts/tec/README.md new file mode 100644 index 0000000000..f1767869d4 --- /dev/null +++ b/contracts/tec/README.md @@ -0,0 +1,73 @@ +## Extensions + +This package implements various extensions to the 0x protocol. Extension contracts can add various rules around how orders are settled while still getting the interoperability and security benefits of using the underlying 0x protocol contracts. Addresses of the deployed contracts can be found in the 0x [wiki](https://0xproject.com/wiki#Deployed-Addresses) or the [DEPLOYS](./DEPLOYS.json) file within this package. + +## Installation + +**Install** + +```bash +npm install @0x/contracts-extensions --save +``` + +## Bug bounty + +A bug bounty for the 2.0.0 contracts is ongoing! Instructions can be found [here](https://0xproject.com/wiki#Bug-Bounty). + +## Contributing + +We strongly recommend that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository. + +For proposals regarding the 0x protocol's smart contract architecture, message format, or additional functionality, go to the [0x Improvement Proposals (ZEIPs)](https://github.com/0xProject/ZEIPs) repository and follow the contribution guidelines provided therein. + +Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started. + +### Install Dependencies + +If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them: + +```bash +yarn config set workspaces-experimental true +``` + +Then install dependencies + +```bash +yarn install +``` + +### Build + +To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory: + +```bash +PKG=@0x/contracts-extensions yarn build +``` + +Or continuously rebuild on change: + +```bash +PKG=@0x/contracts-extensions yarn watch +``` + +### Clean + +```bash +yarn clean +``` + +### Lint + +```bash +yarn lint +``` + +### Run Tests + +```bash +yarn test +``` + +#### Testing options + +Contracts testing options like coverage, profiling, revert traces or backing node choosing - are described [here](../TESTING.md). diff --git a/contracts/tec/compiler.json b/contracts/tec/compiler.json new file mode 100644 index 0000000000..66a3b85d12 --- /dev/null +++ b/contracts/tec/compiler.json @@ -0,0 +1,22 @@ +{ + "artifactsDir": "./generated-artifacts", + "contractsDir": "./contracts", + "compilerSettings": { + "optimizer": { + "enabled": true, + "runs": 1000000 + }, + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode.object", + "evm.bytecode.sourceMap", + "evm.deployedBytecode.object", + "evm.deployedBytecode.sourceMap" + ] + } + } + }, + "contracts": ["TEC"] +} diff --git a/contracts/tec/contracts/src/MixinSignatureValidator.sol b/contracts/tec/contracts/src/MixinSignatureValidator.sol new file mode 100644 index 0000000000..e530a4321d --- /dev/null +++ b/contracts/tec/contracts/src/MixinSignatureValidator.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.5.2; + +import "@0x/contract-utils/contracts/src/LibBytes.sol"; + + +contract MixinSignatureValidator { + + using LibBytes for bytes; + + /// @dev Recovers the address of a signer given a hash and signature. + /// @param hash Any 32 byte hash. + /// @param signature Proof that the hash has been signed by signer. + function getSignerAddress(bytes32 hash, bytes memory signature) + internal + pure + returns (address signerAddress) + { + require( + signature.length > 0, + "LENGTH_GREATER_THAN_0_REQUIRED" + ); + + // Pop last byte off of signature byte array. + uint8 signatureTypeRaw = uint8(signature.popLastByte()); + + // Ensure signature is supported + require( + signatureTypeRaw < uint8(SignatureType.NSignatureTypes), + "SIGNATURE_UNSUPPORTED" + ); + + SignatureType signatureType = SignatureType(signatureTypeRaw); + + // Always illegal signature. + // This is always an implicit option since a signer can create a + // signature array with invalid type or length. We may as well make + // it an explicit option. This aids testing and analysis. It is + // also the initialization value for the enum type. + if (signatureType == SignatureType.Illegal) { + revert("SIGNATURE_ILLEGAL"); + + // Signature using EIP712 + } else if (signatureType == SignatureType.EIP712) { + require( + signature.length == 65, + "LENGTH_65_REQUIRED" + ); + uint8 v = uint8(signature[0]); + bytes32 r = signature.readBytes32(1); + bytes32 s = signature.readBytes32(33); + signerAddress = ecrecover( + hash, + v, + r, + s + ); + return signerAddress; + + // Signed using web3.eth_sign + } else if (signatureType == SignatureType.EthSign) { + require( + signature.length == 65, + "LENGTH_65_REQUIRED" + ); + uint8 v = uint8(signature[0]); + bytes32 r = signature.readBytes32(1); + bytes32 s = signature.readBytes32(33); + signerAddress = ecrecover( + keccak256(abi.encodePacked( + "\x19Ethereum Signed Message:\n32", + hash + )), + v, + r, + s + ); + return signerAddress; + } + } +} \ No newline at end of file diff --git a/contracts/tec/contracts/src/MixinTECApprovalVerifier.sol b/contracts/tec/contracts/src/MixinTECApprovalVerifier.sol new file mode 100644 index 0000000000..4f5a41e592 --- /dev/null +++ b/contracts/tec/contracts/src/MixinTECApprovalVerifier.sol @@ -0,0 +1,232 @@ +/* + + 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.5.2; +pragma experimental "ABIEncoderV2"; + +import "@0x/contracts-exchange-libs/contracts/src/LibExchangeSelectors.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; +import "@0x/contracts-utils/contracts/src/LibAddressArray.sol"; +import "@0x/contracts-utils/contracts/src/LibBytes.sol"; +import "./libs/LibTECApproval.sol"; +import "./libs/LibZeroExTransaction.sol"; +import "./mixins/MSignatureValidator.sol"; +import "./mixins/MTECApprovalVerifier.sol"; + + +contract MixinTECApprovalVerifier is + LibExchangeSelectors, + LibTECApproval, + LibZeroExTransaction, + MSignatureValidator, + MTECApprovalVerifier +{ + using LibAddressArray for address[]; + using LibBytes for bytes; + + /// @dev Validates that the 0x transaction has been approved by all of the feeRecipients + /// that correspond to each order in the transaction's Exchange calldata. + /// @param transaction 0x transaction containing salt, signerAddress, and data. + /// @param transactionSignature Proof that the transaction has been signed by the signer. + /// @param approvalExpirationTimeSeconds Array of expiration times in seconds for which each corresponding approval signature expires. + /// @param approvalSignatures Array of signatures that correspond to the feeRecipients of each order in the transaction's Exchange calldata. + function assertValidTECApproval( + LibZeroExTransaction.ZeroExTransaction memory transaction, + bytes memory transactionSignature, + uint256[] memory approvalExpirationTimeSeconds, + bytes[] memory approvalSignatures + ) + internal + view + { + // Hash 0x transaction + bytes32 transactionHash = getTransactionHash(transaction); + + // Get the function selector of the Exchange calldata in the 0x transaction + bytes4 exchangeFunctionSelector = transaction.data.readBytes4(0); + + if ( + exchangeFunctionSelector == FILL_ORDER_SELECTOR || + exchangeFunctionSelector == FILL_ORDER_NO_THROW_SELECTOR || + exchangeFunctionSelector == FILL_OR_KILL_ORDER_SELECTOR || + exchangeFunctionSelector == CANCEL_ORDER_SELECTOR + ) { + // Decode single order + (LibOrder.Order memory order) = abi.decode( + transaction.data, + (LibOrder.Order) + ); + + // Revert if approval is invalid for single order + assertValidSingleOrderApproval( + order, + transactionHash, + transactionSignature, + approvalExpirationTimeSeconds[0], + approvalSignatures[0] + ); + } else if ( + exchangeFunctionSelector == BATCH_FILL_ORDERS_SELECTOR || + exchangeFunctionSelector == BATCH_FILL_ORDERS_NO_THROW_SELECTOR || + exchangeFunctionSelector == BATCH_FILL_OR_KILL_ORDERS_SELECTOR || + exchangeFunctionSelector == MARKET_BUY_ORDERS_SELECTOR || + exchangeFunctionSelector == MARKET_BUY_ORDERS_NO_THROW_SELECTOR || + exchangeFunctionSelector == MARKET_SELL_ORDERS_SELECTOR || + exchangeFunctionSelector == MARKET_SELL_ORDERS_NO_THROW_SELECTOR || + exchangeFunctionSelector == BATCH_CANCEL_ORDERS_SELECTOR + ) { + // Decode all orders + (LibOrder.Order[] memory orders) = abi.decode( + transaction.data, + (LibOrder.Order[]) + ); + + // Revert if approval is invalid for batch of orders + assertValidBatchOrderApproval( + orders, + transactionHash, + transactionSignature, + approvalExpirationTimeSeconds, + approvalSignatures + ); + } else if (exchangeFunctionSelector == MATCH_ORDERS_SELECTOR) { + // Decode left and right orders + (LibOrder.Order memory leftOrder, LibOrder.Order memory rightOrder) = abi.decode( + transaction.data, + (LibOrder.Order, LibOrder.Order) + ); + + // Create array of orders + LibOrder.Order[] memory orders = new LibOrder.Order[](2); + orders[0] = leftOrder; + orders[1] = rightOrder; + + // Revert if approval is invalid for batch of orders + assertValidBatchOrderApproval( + orders, + transactionHash, + transactionSignature, + approvalExpirationTimeSeconds, + approvalSignatures + ); + } else if (exchangeFunctionSelector == CANCEL_ORDERS_UP_TO_SELECTOR) { + // `cancelOrdersUpTo` is always permitted + return; + } else { + revert("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR"); + } + } + + + /// @dev Validates that the feeRecipient of a single order has approved a 0x transaction. + /// @param order Order struct containing order specifications. + /// @param transactionHash EIP712 hash of the 0x transaction. + /// @param transactionSignature Proof that the transaction has been signed by the signer. + /// @param approvalExpirationTimeSeconds Expiration times in seconds for which the approval signature expires. + /// @param approvalSignature Signatures that corresponds to the feeRecipient of the order. + function assertValidSingleOrderApproval( + LibOrder.Order memory order, + bytes32 transactionHash, + bytes memory transactionSignature, + uint256 approvalExpirationTimeSeconds, + bytes memory approvalSignature + ) + internal + view + { + // Create approval message + TECApproval memory approval = TECApproval({ + transactionHash: transactionHash, + transactionSignature: transactionSignature, + approvalExpirationTimeSeconds: approvalExpirationTimeSeconds + }); + + // Revert if approval expired + require( + // solhint-disable-next-line not-rely-on-time + approvalExpirationTimeSeconds > block.timestamp, + "APPROVAL_EXPIRED" + ); + + // Hash approval message and recover signer address + bytes32 approvalHash = getTECApprovalHash(approval); + address approvalSignerAddress = getAddressFromSignature(approvalHash, approvalSignature); + + // Revert if signer of approval is not the feeRecipient of order + require( + order.feeRecipientAddress == approvalSignerAddress, + "INVALID_APPROVAL_SIGNATURE" + ); + } + + /// @dev Validates that the feeRecipient of a single order has approved a 0x transaction. + /// @param orders Array of order structs containing order specifications. + /// @param transactionHash EIP712 hash of the 0x transaction. + /// @param transactionSignature Proof that the transaction has been signed by the signer. + /// @param approvalExpirationTimeSeconds Array of expiration times in seconds for which each corresponding approval signature expires. + /// @param approvalSignatures Array of signatures that correspond to the feeRecipients of each order. + function assertValidBatchOrderApproval( + LibOrder.Order[] memory orders, + bytes32 transactionHash, + bytes memory transactionSignature, + uint256[] memory approvalExpirationTimeSeconds, + bytes[] memory approvalSignatures + ) + internal + view + { + // Create empty list of approval signers + address[] memory approvalSignerAddresses = new address[](0); + + uint256 signaturesLength = approvalSignatures.length; + for (uint256 i = 0; i < signaturesLength; i++) { + // Create approval message + TECApproval memory approval = TECApproval({ + transactionHash: transactionHash, + transactionSignature: transactionSignature, + approvalExpirationTimeSeconds: approvalExpirationTimeSeconds[i] + }); + + // Ensure approval has not expired + require( + // solhint-disable-next-line not-rely-on-time + approval.approvalExpirationTimeSeconds > block.timestamp, + "APPROVAL_EXPIRED" + ); + + // Hash approval message and recover signer address + bytes32 approvalHash = getTECApprovalHash(approval); + address approvalSignerAddress = getAddressFromSignature(approvalHash, approvalSignatures[i]); + + // Add approval signer to list of signers + approvalSignerAddresses.append(approvalSignerAddress); + } + + uint256 ordersLength = orders.length; + for (uint256 i = 0; i < ordersLength; i++) { + // Get index of feeRecipient in list of approval signers + (bool doesExist,) = approvalSignerAddresses.indexOf(orders[i].feeRecipientAddress); + + // Ensure approval signer exists + require( + doesExist, + "INVALID_APPROVAL_SIGNATURE" + ); + } + } +} diff --git a/contracts/tec/contracts/src/MixinTECCore.sol b/contracts/tec/contracts/src/MixinTECCore.sol new file mode 100644 index 0000000000..801857c459 --- /dev/null +++ b/contracts/tec/contracts/src/MixinTECCore.sol @@ -0,0 +1,62 @@ +/* + + 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.5.2; +pragma experimental "ABIEncoderV2"; + +import "./libs/LibZeroExTransaction.sol"; +import "./libs/LibConstants.sol"; +import "./mixins/MTECApprovalVerifier.sol"; +import "./interfaces/ITECCore.sol"; + + +contract MixinTECCore is + LibConstants, + MTECApprovalVerifier, + ITECCore +{ + /// @dev Executes a 0x transaction that has been signed by the feeRecipients that correspond to each order in the transaction's Exchange calldata. + /// @param transaction 0x transaction containing salt, signerAddress, and data. + /// @param transactionSignature Proof that the transaction has been signed by the signer. + /// @param approvalExpirationTimeSeconds Array of expiration times in seconds for which each corresponding approval signature expires. + /// @param approvalSignatures Array of signatures that correspond to the feeRecipients of each order in the transaction's Exchange calldata. + function executeTransaction( + LibZeroExTransaction.ZeroExTransaction memory transaction, + bytes memory transactionSignature, + uint256[] memory approvalExpirationTimeSeconds, + bytes[] memory approvalSignatures + ) + public + { + // Validate that the 0x transaction has been approves by each feeRecipient + assertValidTECApproval( + transaction, + transactionSignature, + approvalExpirationTimeSeconds, + approvalSignatures + ); + + // Execute the transaction + EXCHANGE.executeTransaction( + transaction.salt, + transaction.signerAddress, + transaction.data, + transactionSignature + ); + } +} diff --git a/contracts/tec/contracts/src/TEC.sol b/contracts/tec/contracts/src/TEC.sol new file mode 100644 index 0000000000..479d001f54 --- /dev/null +++ b/contracts/tec/contracts/src/TEC.sol @@ -0,0 +1,36 @@ +/* + + 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.5.2; + +import "./libs/LibConstants.sol"; +import "./MixinTECApprovalVerifier.sol"; +import "./MixinTECCore.sol"; + + +// solhint-disable no-empty-blocks +contract TEC is + LibConstants, + MixinTECApprovalVerifier, + MixinTECCore +{ + constructor (address _exchange) + public + LibConstants(_exchange) + {} +} diff --git a/contracts/tec/contracts/src/interfaces/IExchange.sol b/contracts/tec/contracts/src/interfaces/IExchange.sol new file mode 100644 index 0000000000..229e65943f --- /dev/null +++ b/contracts/tec/contracts/src/interfaces/IExchange.sol @@ -0,0 +1,35 @@ +/* + + 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.5.2; + + +contract IExchange { + + /// @dev Executes an exchange method call in the context of signer. + /// @param salt Arbitrary number to ensure uniqueness of transaction hash. + /// @param signerAddress Address of transaction signer. + /// @param data AbiV2 encoded calldata. + /// @param signature Proof of signer transaction by signer. + function executeTransaction( + uint256 salt, + address signerAddress, + bytes calldata data, + bytes calldata signature + ) + external; +} \ No newline at end of file diff --git a/contracts/tec/contracts/src/interfaces/ITECCore.sol b/contracts/tec/contracts/src/interfaces/ITECCore.sol new file mode 100644 index 0000000000..6e18b968f2 --- /dev/null +++ b/contracts/tec/contracts/src/interfaces/ITECCore.sol @@ -0,0 +1,39 @@ +/* + + 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.5.2; +pragma experimental "ABIEncoderV2"; + +import "../libs/LibZeroExTransaction.sol"; + + +contract ITECCore { + + /// @dev Executes a 0x transaction that has been signed by the feeRecipients that correspond to each order in the transaction's Exchange calldata. + /// @param transaction 0x transaction containing salt, signerAddress, and data. + /// @param transactionSignature Proof that the transaction has been signed by the signer. + /// @param approvalExpirationTimeSeconds Array of expiration times in seconds for which each corresponding approval signature expires. + /// @param approvalSignatures Array of signatures that correspond to the feeRecipients of each order in the transaction's Exchange calldata. + function executeTransaction( + LibZeroExTransaction.ZeroExTransaction memory transaction, + bytes memory transactionSignature, + uint256[] memory approvalExpirationTimeSeconds, + bytes[] memory approvalSignatures + ) + public; +} diff --git a/contracts/tec/contracts/src/libs/LibConstants.sol b/contracts/tec/contracts/src/libs/LibConstants.sol new file mode 100644 index 0000000000..58442c570d --- /dev/null +++ b/contracts/tec/contracts/src/libs/LibConstants.sol @@ -0,0 +1,34 @@ +/* + + 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.5.2; + +import "../interfaces/IExchange.sol"; + + +contract LibConstants { + + // solhint-disable-next-line var-name-mixedcase + IExchange internal EXCHANGE; + + constructor (address _exchange) + public + { + EXCHANGE = IExchange(_exchange); + } +} diff --git a/contracts/tec/contracts/src/libs/LibEIP712Domain.sol b/contracts/tec/contracts/src/libs/LibEIP712Domain.sol new file mode 100644 index 0000000000..e36af24811 --- /dev/null +++ b/contracts/tec/contracts/src/libs/LibEIP712Domain.sol @@ -0,0 +1,87 @@ +/* + + 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.5.2; + + +contract LibEIP712Domain { + + // EIP191 header for EIP712 prefix + string constant internal EIP191_HEADER = "\x19\x01"; + + // EIP712 Domain Name value + string constant internal EIP712_DOMAIN_NAME = "0x Protocol Trade Execution Coordinator"; + + // EIP712 Domain Version value + string constant internal EIP712_DOMAIN_VERSION = "1.0.0"; + + // Hash of the EIP712 Domain Separator Schema + bytes32 constant internal EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH = keccak256(abi.encodePacked( + "EIP712Domain(", + "string name,", + "string version,", + "address verifyingContract", + ")" + )); + + // Hash of the EIP712 Domain Separator data + // solhint-disable-next-line var-name-mixedcase + bytes32 public EIP712_DOMAIN_HASH; + + constructor () + public + { + EIP712_DOMAIN_HASH = keccak256(abi.encodePacked( + EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH, + keccak256(bytes(EIP712_DOMAIN_NAME)), + keccak256(bytes(EIP712_DOMAIN_VERSION)), + uint256(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 result) + { + bytes32 eip712DomainHash = EIP712_DOMAIN_HASH; + + // Assembly for more efficient computing: + // keccak256(abi.encodePacked( + // EIP191_HEADER, + // EIP712_DOMAIN_HASH, + // hashStruct + // )); + + assembly { + // Load free memory pointer + let memPtr := mload(64) + + mstore(memPtr, 0x1901000000000000000000000000000000000000000000000000000000000000) // EIP191 header + mstore(add(memPtr, 2), eip712DomainHash) // EIP712 domain hash + mstore(add(memPtr, 34), hashStruct) // Hash of struct + + // Compute hash + result := keccak256(memPtr, 66) + } + return result; + } +} diff --git a/contracts/tec/contracts/src/libs/LibTECApproval.sol b/contracts/tec/contracts/src/libs/LibTECApproval.sol new file mode 100644 index 0000000000..6cdc2a709f --- /dev/null +++ b/contracts/tec/contracts/src/libs/LibTECApproval.sol @@ -0,0 +1,89 @@ +/* + + 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.5.2; + +import "./LibEIP712Domain.sol"; + + +contract LibTECApproval is + LibEIP712Domain +{ + // Hash for the EIP712 TEC approval message + bytes32 constant internal EIP712_TEC_APPROVAL_SCHEMA_HASH = keccak256(abi.encodePacked( + "TECApproval(", + "bytes32 transactionHash,", + "bytes transactionSignature,", + "uint256 approvalExpirationTimeSeconds", + ")" + )); + + struct TECApproval { + bytes32 transactionHash; // EIP712 hash of the transaction, using the domain separator of this contract. + bytes transactionSignature; // Signature of the 0x transaction. + uint256 approvalExpirationTimeSeconds; // Timestamp in seconds for which the signature expires. + } + + /// @dev Calculated the EIP712 hash of the TEC approval mesasage using the domain separator of this contract. + /// @param approval TEC approval message containing the transaction hash, transaction signature, and expiration of the approval. + /// @return EIP712 hash of the TEC approval message with the domain separator of this contract. + function getTECApprovalHash(TECApproval memory approval) + internal + view + returns (bytes32 approvalHash) + { + approvalHash = hashEIP712Message(hashTECApproval(approval)); + return approvalHash; + } + + /// @dev Calculated the EIP712 hash of the TEC approval mesasage with no domain separator. + /// @param approval TEC approval message containing the transaction hash, transaction signature, and expiration of the approval. + /// @return EIP712 hash of the TEC approval message with no domain separator. + function hashTECApproval(TECApproval memory approval) + internal + pure + returns (bytes32 result) + { + bytes32 schemaHash = EIP712_TEC_APPROVAL_SCHEMA_HASH; + bytes32 transactionSignatureHash = keccak256(approval.transactionSignature); + // TODO(abandeali1): optimize by loading from memory in assembly + bytes32 transactionHash = approval.transactionHash; + uint256 approvalExpirationTimeSeconds = approval.approvalExpirationTimeSeconds; + + // Assembly for more efficiently computing: + // keccak256(abi.encodePacked( + // EIP712_TEC_APPROVAL_SCHEMA_HASH, + // approval.transactionHash, + // keccak256(approval.transactionSignature) + // approval.expiration, + // )); + + assembly { + // Load free memory pointer + let memPtr := mload(64) + + mstore(memPtr, schemaHash) // hash of schema + mstore(add(memPtr, 32), transactionHash) // transactionHash + mstore(add(memPtr, 64), transactionSignatureHash) // transactionSignatureHash + mstore(add(memPtr, 96), approvalExpirationTimeSeconds) // approvalExpirationTimeSeconds + // Compute hash + result := keccak256(memPtr, 128) + } + return result; + } +} \ No newline at end of file diff --git a/contracts/tec/contracts/src/libs/LibZeroExTransaction.sol b/contracts/tec/contracts/src/libs/LibZeroExTransaction.sol new file mode 100644 index 0000000000..b004c7e110 --- /dev/null +++ b/contracts/tec/contracts/src/libs/LibZeroExTransaction.sol @@ -0,0 +1,91 @@ +/* + + 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.5.2; + +import "./LibEIP712Domain.sol"; + + +contract LibZeroExTransaction is + LibEIP712Domain +{ + // Hash for the EIP712 0x transaction schema + bytes32 constant internal EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH = keccak256(abi.encodePacked( + "ZeroExTransaction(", + "uint256 salt,", + "address signerAddress,", + "bytes data", + ")" + )); + + struct ZeroExTransaction { + uint256 salt; // Arbitrary number to ensure uniqueness of transaction hash. + address signerAddress; // Address of transaction signer. + bytes data; // AbiV2 encoded calldata. + } + + /// @dev Calculates the EIP712 hash of a 0x transaction using the domain separator of this contract. + /// @param transaction 0x transaction containing salt, signerAddress, and data. + /// @return EIP712 hash of the transaction with the domain separator of this contract. + function getTransactionHash(ZeroExTransaction memory transaction) + internal + view + returns (bytes32 transactionHash) + { + // Note: this transaction hash will differ from the hash produced by the Exchange contract because it utilizes a different domain hash. + transactionHash = hashEIP712Message(hashZeroExTransaction(transaction)); + return transactionHash; + } + + /// @dev Calculates EIP712 hash of the 0x transaction with no domain separator. + /// @param transaction 0x transaction containing salt, signerAddress, and data. + /// @return EIP712 hash of the transaction with no domain separator. + function hashZeroExTransaction(ZeroExTransaction memory transaction) + internal + pure + returns (bytes32 result) + { + bytes32 schemaHash = EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH; + bytes32 dataHash = keccak256(transaction.data); + // TODO(abandeali1): optimize by loading from memory in assembly + uint256 salt = transaction.salt; + address signerAddress = transaction.signerAddress; + + // Assembly for more efficiently computing: + // keccak256(abi.encodePacked( + // EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH, + // transaction.salt, + // uint256(transaction.signerAddress), + // keccak256(transaction.data) + // )); + + assembly { + // Load free memory pointer + let memPtr := mload(64) + + mstore(memPtr, schemaHash) // hash of schema + mstore(add(memPtr, 32), salt) // salt + mstore(add(memPtr, 64), and(signerAddress, 0xffffffffffffffffffffffffffffffffffffffff)) // signerAddress + mstore(add(memPtr, 96), dataHash) // hash of data + + // Compute hash + result := keccak256(memPtr, 128) + } + return result; + } +} \ No newline at end of file diff --git a/contracts/tec/contracts/src/mixins/MSignatureValidator.sol b/contracts/tec/contracts/src/mixins/MSignatureValidator.sol new file mode 100644 index 0000000000..91917fc1ef --- /dev/null +++ b/contracts/tec/contracts/src/mixins/MSignatureValidator.sol @@ -0,0 +1,39 @@ +/* + + 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.5.2; + + +contract MSignatureValidator { + + // Allowed signature types. + enum SignatureType { + Illegal, // 0x00, default value + EIP712, // 0x01 + EthSign, // 0x02 + NSignatureTypes // 0x03, number of signature types. Always leave at end. + } + + /// @dev Recovers the address of a signer given a hash and signature. + /// @param hash Any 32 byte hash. + /// @param signature Proof that the hash has been signed by signer. + function getAddressFromSignature(bytes32 hash, bytes memory signature) + internal + pure + returns (address signerAddress); +} \ No newline at end of file diff --git a/contracts/tec/contracts/src/mixins/MTECApprovalVerifier.sol b/contracts/tec/contracts/src/mixins/MTECApprovalVerifier.sol new file mode 100644 index 0000000000..88cd8b883a --- /dev/null +++ b/contracts/tec/contracts/src/mixins/MTECApprovalVerifier.sol @@ -0,0 +1,74 @@ +/* + + 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.5.2; +pragma experimental "ABIEncoderV2"; + +import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; +import "../libs/LibZeroExTransaction.sol"; + + +contract MTECApprovalVerifier { + + /// @dev Validates that the 0x transaction has been approved by all of the feeRecipients + /// that correspond to each order in the transaction's Exchange calldata. + /// @param transaction 0x transaction containing salt, signerAddress, and data. + /// @param transactionSignature Proof that the transaction has been signed by the signer. + /// @param approvalExpirationTimeSeconds Array of expiration times in seconds for which each corresponding approval signature expires. + /// @param approvalSignatures Array of signatures that correspond to the feeRecipients of each order in the transaction's Exchange calldata. + function assertValidTECApproval( + LibZeroExTransaction.ZeroExTransaction memory transaction, + bytes memory transactionSignature, + uint256[] memory approvalExpirationTimeSeconds, + bytes[] memory approvalSignatures + ) + internal + view; + + /// @dev Validates that the feeRecipient of a single order has approved a 0x transaction. + /// @param order Order struct containing order specifications. + /// @param transactionHash EIP712 hash of the 0x transaction. + /// @param transactionSignature Proof that the transaction has been signed by the signer. + /// @param approvalExpirationTimeSeconds Expiration times in seconds for which the approval signature expires. + /// @param approvalSignature Signatures that corresponds to the feeRecipient of the order. + function assertValidSingleOrderApproval( + LibOrder.Order memory order, + bytes32 transactionHash, + bytes memory transactionSignature, + uint256 approvalExpirationTimeSeconds, + bytes memory approvalSignature + ) + internal + view; + + /// @dev Validates that the feeRecipient of a single order has approved a 0x transaction. + /// @param orders Array of order structs containing order specifications. + /// @param transactionHash EIP712 hash of the 0x transaction. + /// @param transactionSignature Proof that the transaction has been signed by the signer. + /// @param approvalExpirationTimeSeconds Array of expiration times in seconds for which each corresponding approval signature expires. + /// @param approvalSignatures Array of signatures that correspond to the feeRecipients of each order. + function assertValidBatchOrderApproval( + LibOrder.Order[] memory orders, + bytes32 transactionHash, + bytes memory transactionSignature, + uint256[] memory approvalExpirationTimeSeconds, + bytes[] memory approvalSignatures + ) + internal + view; +} diff --git a/contracts/tec/package.json b/contracts/tec/package.json new file mode 100644 index 0000000000..a3319d9cad --- /dev/null +++ b/contracts/tec/package.json @@ -0,0 +1,85 @@ +{ + "name": "@0x/contracts-tec", + "version": "0.0.1", + "engines": { + "node": ">=6.12" + }, + "description": "Smart contract extensions of 0x protocol", + "main": "lib/src/index.js", + "directories": { + "test": "test" + }, + "scripts": { + "build": "yarn pre_build && tsc -b", + "build:ci": "yarn build", + "pre_build": "run-s compile generate_contract_wrappers", + "test": "yarn run_mocha", + "rebuild_and_test": "run-s build test", + "test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov", + "test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html", + "test:trace": "SOLIDITY_REVERT_TRACE=true run-s build run_mocha", + "run_mocha": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 100000 --bail --exit", + "compile": "sol-compiler", + "watch": "sol-compiler -w", + "clean": "shx rm -rf lib generated-artifacts generated-wrappers", + "generate_contract_wrappers": "abi-gen --abis ${npm_package_config_abis} --template ../../node_modules/@0x/abi-gen-templates/contract.handlebars --partials '../../node_modules/@0x/abi-gen-templates/partials/**/*.handlebars' --output generated-wrappers --backend ethers", + "lint": "tslint --format stylish --project . --exclude ./generated-wrappers/**/* --exclude ./generated-artifacts/**/* --exclude **/lib/**/* && yarn lint-contracts", + "coverage:report:text": "istanbul report text", + "coverage:report:html": "istanbul report html && open coverage/index.html", + "profiler:report:html": "istanbul report html && open coverage/index.html", + "coverage:report:lcov": "istanbul report lcov", + "test:circleci": "yarn test", + "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol" + }, + "config": { + "abis": "generated-artifacts/@(TEC).json" + }, + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x-monorepo.git" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x-monorepo/issues" + }, + "homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md", + "devDependencies": { + "@0x/abi-gen": "^1.0.22", + "@0x/contract-wrappers": "^5.0.1", + "@0x/contracts-test-utils": "^2.0.1", + "@0x/dev-utils": "^1.0.24", + "@0x/sol-compiler": "^2.0.2", + "@0x/tslint-config": "^2.0.2", + "@types/lodash": "4.14.104", + "@types/node": "*", + "chai": "^4.0.1", + "chai-as-promised": "^7.1.0", + "chai-bignumber": "^3.0.0", + "dirty-chai": "^2.0.1", + "make-promises-safe": "^1.1.0", + "mocha": "^4.1.0", + "npm-run-all": "^4.1.2", + "shx": "^0.2.2", + "solhint": "^1.4.1", + "tslint": "5.11.0", + "typescript": "3.0.1" + }, + "dependencies": { + "@0x/base-contract": "^3.0.13", + "@0x/contracts-exchange-libs": "^1.0.0", + "@0x/contracts-exchange": "^1.0.0", + "@0x/contracts-erc20": "^1.0.0", + "@0x/contracts-erc721": "^1.0.0", + "@0x/contracts-utils": "^3.0.0", + "@0x/order-utils": "^3.1.2", + "@0x/types": "^1.5.2", + "@0x/typescript-typings": "^3.0.8", + "@0x/utils": "^3.0.1", + "@0x/web3-wrapper": "^3.2.4", + "ethereum-types": "^1.1.6", + "lodash": "^4.17.5" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/contracts/tec/src/artifacts/index.ts b/contracts/tec/src/artifacts/index.ts new file mode 100644 index 0000000000..c2bb7a21d5 --- /dev/null +++ b/contracts/tec/src/artifacts/index.ts @@ -0,0 +1,7 @@ +import { ContractArtifact } from 'ethereum-types'; + +import * as TEC from '../../generated-artifacts/TEC.json'; + +export const artifacts = { + TEC: TEC as ContractArtifact, +}; diff --git a/contracts/tec/src/index.ts b/contracts/tec/src/index.ts new file mode 100644 index 0000000000..2c130c21e9 --- /dev/null +++ b/contracts/tec/src/index.ts @@ -0,0 +1,3 @@ +export * from './artifacts'; +export * from './wrappers'; +// export * from '../test/utils'; diff --git a/contracts/tec/src/wrappers/index.ts b/contracts/tec/src/wrappers/index.ts new file mode 100644 index 0000000000..326e5bc23c --- /dev/null +++ b/contracts/tec/src/wrappers/index.ts @@ -0,0 +1 @@ +export * from '../../generated-wrappers/tec'; diff --git a/contracts/tec/test/global_hooks.ts b/contracts/tec/test/global_hooks.ts new file mode 100644 index 0000000000..f8ace376a5 --- /dev/null +++ b/contracts/tec/test/global_hooks.ts @@ -0,0 +1,17 @@ +import { env, EnvVars } from '@0x/dev-utils'; + +import { coverage, profiler, provider } from '@0x/contracts-test-utils'; +before('start web3 provider', () => { + provider.start(); +}); +after('generate coverage report', async () => { + if (env.parseBoolean(EnvVars.SolidityCoverage)) { + const coverageSubprovider = coverage.getCoverageSubproviderSingleton(); + await coverageSubprovider.writeCoverageAsync(); + } + if (env.parseBoolean(EnvVars.SolidityProfiler)) { + const profilerSubprovider = profiler.getProfilerSubproviderSingleton(); + await profilerSubprovider.writeProfilerOutputAsync(); + } + provider.stop(); +}); diff --git a/contracts/tec/tsconfig.json b/contracts/tec/tsconfig.json new file mode 100644 index 0000000000..50ebbe9a67 --- /dev/null +++ b/contracts/tec/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib", + "rootDir": ".", + "resolveJsonModule": true + }, + "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], + "files": ["./generated-artifacts/TEC.json"], + "exclude": ["./deploy/solc/solc_bin"] +} diff --git a/contracts/tec/tslint.json b/contracts/tec/tslint.json new file mode 100644 index 0000000000..1bb3ac2a22 --- /dev/null +++ b/contracts/tec/tslint.json @@ -0,0 +1,6 @@ +{ + "extends": ["@0x/tslint-config"], + "rules": { + "custom-no-magic-numbers": false + } +}