diff options
Diffstat (limited to 'packages/contracts/src')
11 files changed, 284 insertions, 48 deletions
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol b/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol index 5ccb06b88..caf48bfca 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol @@ -24,13 +24,15 @@ import "./MixinSignatureValidator.sol"; import "./MixinSettlement.sol"; import "./MixinWrapperFunctions.sol"; import "./MixinAssetProxyDispatcher.sol"; +import "./MixinTransactions.sol"; contract Exchange is MixinExchangeCore, MixinSignatureValidator, MixinSettlement, MixinWrapperFunctions, - MixinAssetProxyDispatcher + MixinAssetProxyDispatcher, + MixinTransactions { string constant public VERSION = "2.0.1-alpha"; @@ -42,5 +44,6 @@ contract Exchange is MixinSettlement(_zrxProxyData) MixinWrapperFunctions() MixinAssetProxyDispatcher() + MixinTransactions() {} } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/LibOrder.sol b/packages/contracts/src/contracts/current/protocol/Exchange/LibOrder.sol index 61a4a382d..a98d7cbeb 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/LibOrder.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/LibOrder.sol @@ -26,6 +26,7 @@ contract LibOrder { "address makerAddress", "address takerAddress", "address feeRecipientAddress", + "address senderAddress", "uint256 makerAssetAmount", "uint256 takerAssetAmount", "uint256 makerFee", @@ -40,6 +41,7 @@ contract LibOrder { address makerAddress; address takerAddress; address feeRecipientAddress; + address senderAddress; uint256 makerAssetAmount; uint256 takerAssetAmount; uint256 makerFee; @@ -66,6 +68,7 @@ contract LibOrder { order.makerAddress, order.takerAddress, order.feeRecipientAddress, + order.senderAddress, order.makerAssetAmount, order.takerAssetAmount, order.makerFee, diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol index 710315515..80120bc74 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol @@ -22,6 +22,7 @@ pragma experimental ABIEncoderV2; import "./mixins/MExchangeCore.sol"; import "./mixins/MSettlement.sol"; import "./mixins/MSignatureValidator.sol"; +import "./mixins/MTransactions.sol"; import "./LibOrder.sol"; import "./LibErrors.sol"; import "./LibPartialAmount.sol"; @@ -35,6 +36,7 @@ contract MixinExchangeCore is MExchangeCore, MSettlement, MSignatureValidator, + MTransactions, SafeMath, LibErrors, LibPartialAmount @@ -113,10 +115,16 @@ contract MixinExchangeCore is require(order.takerAssetAmount > 0); require(isValidSignature(orderHash, order.makerAddress, signature)); } + + // Validate sender is allowed to fill this order + if (order.senderAddress != address(0)) { + require(order.senderAddress == msg.sender); + } - // Validate taker + // Validate taker is allowed to fill this order + address takerAddress = getCurrentContextAddress(); if (order.takerAddress != address(0)) { - require(order.takerAddress == msg.sender); + require(order.takerAddress == takerAddress); } require(takerAssetFillAmount > 0); @@ -146,21 +154,10 @@ contract MixinExchangeCore is // Settle order (fillResults.makerAssetFilledAmount, fillResults.makerFeePaid, fillResults.takerFeePaid) = - settleOrder(order, msg.sender, fillResults.takerAssetFilledAmount); + settleOrder(order, takerAddress, fillResults.takerAssetFilledAmount); // Log order - emit Fill( - order.makerAddress, - msg.sender, - order.feeRecipientAddress, - fillResults.makerAssetFilledAmount, - fillResults.takerAssetFilledAmount, - fillResults.makerFeePaid, - fillResults.takerFeePaid, - orderHash, - order.makerAssetData, - order.takerAssetData - ); + emitFillEvent(order, takerAddress, orderHash, fillResults); return fillResults; } @@ -178,8 +175,16 @@ contract MixinExchangeCore is // Validate the order require(order.makerAssetAmount > 0); require(order.takerAssetAmount > 0); - require(order.makerAddress == msg.sender); + // Validate sender is allowed to cancel this order + if (order.senderAddress != address(0)) { + require(order.senderAddress == msg.sender); + } + + // Validate transaction signed by maker + address makerAddress = getCurrentContextAddress(); + require(order.makerAddress == makerAddress); + if (block.timestamp >= order.expirationTimeSeconds) { emit ExchangeError(uint8(Errors.ORDER_EXPIRED), orderHash); return false; @@ -233,4 +238,27 @@ contract MixinExchangeCore is isError = errPercentageTimes1000000 > 1000; return isError; } + + /// @dev Logs a Fill event with the given arguments. + /// The sole purpose of this function is to get around the stack variable limit. + function emitFillEvent( + Order memory order, + address takerAddress, + bytes32 orderHash, + FillResults memory fillResults) + internal + { + emit Fill( + order.makerAddress, + takerAddress, + order.feeRecipientAddress, + fillResults.makerAssetFilledAmount, + fillResults.takerAssetFilledAmount, + fillResults.makerFeePaid, + fillResults.takerFeePaid, + orderHash, + order.makerAssetData, + order.takerAssetData + ); + } } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol new file mode 100644 index 000000000..9edb1694f --- /dev/null +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol @@ -0,0 +1,92 @@ +/* + + 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.21; +pragma experimental ABIEncoderV2; + +import "./mixins/MSignatureValidator.sol"; +import "./mixins/MTransactions.sol"; + +contract MixinTransactions is + MSignatureValidator, + MTransactions +{ + + // Mapping of transaction hash => executed + // This prevents transactions from being executed more than once. + mapping (bytes32 => bool) public transactions; + + // Address of current transaction signer + address public currentContextAddress; + + /// @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. + /// @param data AbiV2 encoded calldata. + /// @param signature Proof of signer transaction by signer. + function executeTransaction( + uint256 salt, + address signer, + bytes data, + bytes signature) + external + { + // Prevent reentrancy + require(currentContextAddress == address(0)); + + // Calculate transaction hash + bytes32 transactionHash = keccak256( + address(this), + salt, + data + ); + + // Validate transaction has not been executed + require(!transactions[transactionHash]); + + // TODO: is SignatureType.Caller necessary if we make this check? + if (signer != msg.sender) { + // Validate signature + require(isValidSignature(transactionHash, signer, signature)); + + // Set the current transaction signer + currentContextAddress = signer; + } + + // Execute transaction + transactions[transactionHash] = true; + require(address(this).delegatecall(data)); + + // Reset current transaction signer + // TODO: Check if gas is paid when currentContextAddress is already 0. + currentContextAddress = address(0); + } + + /// @dev The current function will be called in the context of this address (either 0x transaction signer or `msg.sender`). + /// If calling a fill function, this address will represent the taker. + /// If calling a cancel function, this address will represent the maker. + /// @return Signer of 0x transaction if entry point is `executeTransaction`. + /// `msg.sender` if entry point is any other function. + function getCurrentContextAddress() + internal + view + returns (address) + { + address contextAddress = currentContextAddress == address(0) ? msg.sender : currentContextAddress; + return contextAddress; + } +} diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol index 2c0fd0751..4d6ba17dd 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol @@ -75,24 +75,25 @@ contract MixinWrapperFunctions is // | | 0x00 | | 1. offset to order (*) | // | | 0x20 | | 2. takerAssetFillAmount | // | | 0x40 | | 3. offset to signature (*) | - // | Data | | 11 * 32 | order: | - // | | 0x000 | | 1. makerAddress | - // | | 0x020 | | 2. takerAddress | - // | | 0x040 | | 3. feeRecipientAddress | - // | | 0x060 | | 4. makerAssetAmount | - // | | 0x080 | | 5. takerAssetAmount | - // | | 0x0A0 | | 6. makerFeeAmount | - // | | 0x0C0 | | 7. takerFeeAmount | - // | | 0x0E0 | | 8. expirationTimeSeconds | - // | | 0x100 | | 9. salt | - // | | 0x120 | | 10. Offset to makerAssetProxyMetadata (*) | - // | | 0x140 | | 11. Offset to takerAssetProxyMetadata (*) | - // | | 0x160 | 32 | makerAssetProxyMetadata Length | - // | | 0x180 | ** | makerAssetProxyMetadata Contents | - // | | 0x1A0 | 32 | takerAssetProxyMetadata Length | - // | | 0x1C0 | ** | takerAssetProxyMetadata Contents | - // | | 0x1E0 | 32 | signature Length | - // | | 0x200 | ** | signature Contents | + // | Data | | 12 * 32 | order: | + // | | 0x000 | | 1. senderAddress | + // | | 0x020 | | 2. makerAddress | + // | | 0x040 | | 3. takerAddress | + // | | 0x060 | | 4. feeRecipientAddress | + // | | 0x080 | | 5. makerAssetAmount | + // | | 0x0A0 | | 6. takerAssetAmount | + // | | 0x0C0 | | 7. makerFeeAmount | + // | | 0x0E0 | | 8. takerFeeAmount | + // | | 0x100 | | 9. expirationTimeSeconds | + // | | 0x120 | | 10. salt | + // | | 0x140 | | 11. Offset to makerAssetProxyMetadata (*) | + // | | 0x160 | | 12. Offset to takerAssetProxyMetadata (*) | + // | | 0x180 | 32 | makerAssetProxyMetadata Length | + // | | 0x1A0 | ** | makerAssetProxyMetadata Contents | + // | | 0x1C0 | 32 | takerAssetProxyMetadata Length | + // | | 0x1E0 | ** | takerAssetProxyMetadata Contents | + // | | 0x200 | 32 | signature Length | + // | | 0x220 | ** | signature Contents | // * Offsets are calculated from the beginning of the current area: Header, Params, Data: // An offset stored in the Params area is calculated from the beginning of the Params section. @@ -150,19 +151,20 @@ contract MixinWrapperFunctions is mstore(dataAreaEnd, mload(sourceOffset)) // makerAddress mstore(add(dataAreaEnd, 0x20), mload(add(sourceOffset, 0x20))) // takerAddress mstore(add(dataAreaEnd, 0x40), mload(add(sourceOffset, 0x40))) // feeRecipientAddress - mstore(add(dataAreaEnd, 0x60), mload(add(sourceOffset, 0x60))) // makerAssetAmount - mstore(add(dataAreaEnd, 0x80), mload(add(sourceOffset, 0x80))) // takerAssetAmount - mstore(add(dataAreaEnd, 0xA0), mload(add(sourceOffset, 0xA0))) // makerFeeAmount - mstore(add(dataAreaEnd, 0xC0), mload(add(sourceOffset, 0xC0))) // takerFeeAmount - mstore(add(dataAreaEnd, 0xE0), mload(add(sourceOffset, 0xE0))) // expirationTimeSeconds - mstore(add(dataAreaEnd, 0x100), mload(add(sourceOffset, 0x100))) // salt - mstore(add(dataAreaEnd, 0x120), mload(add(sourceOffset, 0x120))) // Offset to makerAssetProxyMetadata - mstore(add(dataAreaEnd, 0x140), mload(add(sourceOffset, 0x140))) // Offset to takerAssetProxyMetadata - dataAreaEnd := add(dataAreaEnd, 0x160) - sourceOffset := add(sourceOffset, 0x160) + mstore(add(dataAreaEnd, 0x60), mload(add(sourceOffset, 0x60))) // senderAddress + mstore(add(dataAreaEnd, 0x80), mload(add(sourceOffset, 0x80))) // makerAssetAmount + mstore(add(dataAreaEnd, 0xA0), mload(add(sourceOffset, 0xA0))) // takerAssetAmount + mstore(add(dataAreaEnd, 0xC0), mload(add(sourceOffset, 0xC0))) // makerFeeAmount + mstore(add(dataAreaEnd, 0xE0), mload(add(sourceOffset, 0xE0))) // takerFeeAmount + mstore(add(dataAreaEnd, 0x100), mload(add(sourceOffset, 0x100))) // expirationTimeSeconds + mstore(add(dataAreaEnd, 0x120), mload(add(sourceOffset, 0x120))) // salt + mstore(add(dataAreaEnd, 0x140), mload(add(sourceOffset, 0x140))) // Offset to makerAssetProxyMetadata + mstore(add(dataAreaEnd, 0x160), mload(add(sourceOffset, 0x160))) // Offset to takerAssetProxyMetadata + dataAreaEnd := add(dataAreaEnd, 0x180) + sourceOffset := add(sourceOffset, 0x180) // Write offset to <order.makerAssetProxyMetadata> - mstore(add(dataAreaStart, mul(9, 0x20)), sub(dataAreaEnd, dataAreaStart)) + mstore(add(dataAreaStart, mul(10, 0x20)), sub(dataAreaEnd, dataAreaStart)) // Calculate length of <order.makerAssetProxyMetadata> arrayLenBytes := mload(sourceOffset) @@ -181,7 +183,7 @@ contract MixinWrapperFunctions is } // Write offset to <order.takerAssetProxyMetadata> - mstore(add(dataAreaStart, mul(10, 0x20)), sub(dataAreaEnd, dataAreaStart)) + mstore(add(dataAreaStart, mul(11, 0x20)), sub(dataAreaEnd, dataAreaStart)) // Calculate length of <order.takerAssetProxyMetadata> arrayLenBytes := mload(sourceOffset) diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MTransactions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MTransactions.sol new file mode 100644 index 000000000..10bfcb035 --- /dev/null +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MTransactions.sol @@ -0,0 +1,46 @@ +/* + + 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.21; +pragma experimental ABIEncoderV2; + +import "./MSignatureValidator.sol"; + +contract MTransactions is MSignatureValidator { + + /// @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. + /// @param data AbiV2 encoded calldata. + /// @param signature Proof of signer transaction by signer. + function executeTransaction( + uint256 salt, + address signer, + bytes data, + bytes signature) + external; + + /// @dev The current function will be called in the context of this address (either 0x transaction signer or `msg.sender`). + /// If calling a fill function, this address will represent the taker. + /// If calling a cancel function, this address will represent the maker. + /// @return Signer of 0x transaction if entry point is `executeTransaction`. + /// `msg.sender` if entry point is any other function. + function getCurrentContextAddress() + internal + view + returns (address); +} diff --git a/packages/contracts/src/utils/exchange_wrapper.ts b/packages/contracts/src/utils/exchange_wrapper.ts index 7d06c321b..000905854 100644 --- a/packages/contracts/src/utils/exchange_wrapper.ts +++ b/packages/contracts/src/utils/exchange_wrapper.ts @@ -9,7 +9,7 @@ import { constants } from './constants'; import { formatters } from './formatters'; import { LogDecoder } from './log_decoder'; import { orderUtils } from './order_utils'; -import { AssetProxyId, SignedOrder } from './types'; +import { AssetProxyId, SignedOrder, SignedTransaction } from './types'; export class ExchangeWrapper { private _exchange: ExchangeContract; @@ -207,6 +207,20 @@ export class ExchangeWrapper { const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash); return tx; } + public async executeTransactionAsync( + signedTx: SignedTransaction, + from: string, + ): Promise<TransactionReceiptWithDecodedLogs> { + const txHash = await this._exchange.executeTransaction.sendTransactionAsync( + signedTx.salt, + signedTx.signer, + signedTx.data, + signedTx.signature, + { from }, + ); + const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash); + return tx; + } public async getOrderHashAsync(signedOrder: SignedOrder): Promise<string> { const order = orderUtils.getOrderStruct(signedOrder); const orderHash = await this._exchange.getOrderHash.callAsync(order); diff --git a/packages/contracts/src/utils/order_factory.ts b/packages/contracts/src/utils/order_factory.ts index 08853054d..044e9b865 100644 --- a/packages/contracts/src/utils/order_factory.ts +++ b/packages/contracts/src/utils/order_factory.ts @@ -19,6 +19,7 @@ export class OrderFactory { ): SignedOrder { const randomExpiration = new BigNumber(Math.floor((Date.now() + Math.random() * 100000000000) / 1000)); const order = ({ + senderAddress: ZeroEx.NULL_ADDRESS, expirationTimeSeconds: randomExpiration, salt: ZeroEx.generatePseudoRandomSalt(), takerAddress: ZeroEx.NULL_ADDRESS, diff --git a/packages/contracts/src/utils/order_utils.ts b/packages/contracts/src/utils/order_utils.ts index 52eb44941..10bbf4f7c 100644 --- a/packages/contracts/src/utils/order_utils.ts +++ b/packages/contracts/src/utils/order_utils.ts @@ -24,6 +24,7 @@ export const orderUtils = { }, getOrderStruct(signedOrder: SignedOrder): OrderStruct { const orderStruct = { + senderAddress: signedOrder.senderAddress, makerAddress: signedOrder.makerAddress, takerAddress: signedOrder.takerAddress, feeRecipientAddress: signedOrder.feeRecipientAddress, @@ -44,6 +45,7 @@ export const orderUtils = { 'address makerAddress', 'address takerAddress', 'address feeRecipientAddress', + 'address senderAddress', 'uint256 makerAssetAmount', 'uint256 takerAssetAmount', 'uint256 makerFee', @@ -58,6 +60,7 @@ export const orderUtils = { order.makerAddress, order.takerAddress, order.feeRecipientAddress, + order.senderAddress, order.makerAssetAmount, order.takerAssetAmount, order.makerFee, diff --git a/packages/contracts/src/utils/transaction_factory.ts b/packages/contracts/src/utils/transaction_factory.ts new file mode 100644 index 000000000..3a4f48330 --- /dev/null +++ b/packages/contracts/src/utils/transaction_factory.ts @@ -0,0 +1,35 @@ +import { ZeroEx } from '0x.js'; +import { BigNumber } from '@0xproject/utils'; +import * as ethUtil from 'ethereumjs-util'; + +import { crypto } from './crypto'; +import { signingUtils } from './signing_utils'; +import { SignatureType, SignedTransaction } from './types'; + +export class TransactionFactory { + private _signer: string; + private _exchangeAddress: string; + private _privateKey: Buffer; + constructor(privateKey: Buffer, exchangeAddress: string) { + this._privateKey = privateKey; + this._exchangeAddress = exchangeAddress; + const signerBuff = ethUtil.privateToAddress(this._privateKey); + this._signer = `0x${signerBuff.toString('hex')}`; + } + public newSignedTransaction( + data: string, + signatureType: SignatureType = SignatureType.Ecrecover, + ): SignedTransaction { + const salt = ZeroEx.generatePseudoRandomSalt(); + const txHash = crypto.solSHA3([this._exchangeAddress, salt, ethUtil.toBuffer(data)]); + const signature = signingUtils.signMessage(txHash, this._privateKey, signatureType); + const signedTx = { + exchangeAddress: this._exchangeAddress, + salt, + signer: this._signer, + data, + signature: `0x${signature.toString('hex')}`, + }; + return signedTx; + } +} diff --git a/packages/contracts/src/utils/types.ts b/packages/contracts/src/utils/types.ts index a23b6b876..934dc52fa 100644 --- a/packages/contracts/src/utils/types.ts +++ b/packages/contracts/src/utils/types.ts @@ -122,6 +122,7 @@ export interface SignedOrder extends UnsignedOrder { } export interface OrderStruct { + senderAddress: string; makerAddress: string; takerAddress: string; feeRecipientAddress: string; @@ -148,3 +149,11 @@ export enum SignatureType { Trezor, Contract, } + +export interface SignedTransaction { + exchangeAddress: string; + salt: BigNumber; + signer: string; + data: string; + signature: string; +} |