Merge branch 'v2-prototype' into feature/combinatorial-testing

* v2-prototype: (22 commits)
  Fix closing parens in liborder
  Update after rebase
  ERC721Proxy Always call safeTransferFrom
  Rename makerEpoch => orderEpoch
  Make cancelOrdersUpTo compatible with sender abstraction
  Update PR template
  Use Image component instead of img tag
  Assembler orderHash function
  Optimize and remove redundant encodePacked
  Fix linting issue
  Fix bug where we do fetch balances on wallet login
  Check network state immediately instead of waiting for delay
  Fix onboarding persisting when changing routes
  Consolidate account state messaging logic
  Only elevate wallet zIndex when onboarding is in progress
  Rebase and update feedback
  Run linter
  Add Portal v2 logging
  Simplified handling of source < 32 edge case
  Basic EIP712 encoder
  ...
This commit is contained in:
Fabio Berger
2018-06-20 13:25:29 +02:00
48 changed files with 1167 additions and 366 deletions

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -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(

View File

@@ -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.

View File

@@ -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));
}
}

View File

@@ -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.
}

View File

@@ -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;
}
}

View File

@@ -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.

View File

@@ -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
);
}
}

View File

@@ -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)

View File

@@ -16,7 +16,7 @@
*/
pragma solidity ^0.4.23;
pragma solidity ^0.4.24;
pragma experimental ABIEncoderV2;
import "../../protocol/Exchange/interfaces/IExchange.sol";

View File

@@ -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)
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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;
}