Merge pull request #498 from 0xProject/feature/contracts/takerAbstraction
Sender abstraction
This commit is contained in:
@@ -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()
|
||||
{}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
35
packages/contracts/src/utils/transaction_factory.ts
Normal file
35
packages/contracts/src/utils/transaction_factory.ts
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
205
packages/contracts/test/exchange/transactions.ts
Normal file
205
packages/contracts/test/exchange/transactions.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
import { ZeroEx } from '0x.js';
|
||||
|
||||
import { BlockchainLifecycle } from '@0xproject/dev-utils';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as chai from 'chai';
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
import * as Web3 from 'web3';
|
||||
|
||||
import { DummyERC20TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c20_token';
|
||||
import { ERC20ProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_proxy';
|
||||
import { ExchangeContract } from '../../src/contract_wrappers/generated/exchange';
|
||||
import { assetProxyUtils } from '../../src/utils/asset_proxy_utils';
|
||||
import { constants } from '../../src/utils/constants';
|
||||
import { ERC20Wrapper } from '../../src/utils/erc20_wrapper';
|
||||
import { ExchangeWrapper } from '../../src/utils/exchange_wrapper';
|
||||
import { OrderFactory } from '../../src/utils/order_factory';
|
||||
import { orderUtils } from '../../src/utils/order_utils';
|
||||
import { TransactionFactory } from '../../src/utils/transaction_factory';
|
||||
import {
|
||||
AssetProxyId,
|
||||
ContractName,
|
||||
ERC20BalancesByOwner,
|
||||
ExchangeContractErrs,
|
||||
OrderStruct,
|
||||
SignatureType,
|
||||
SignedOrder,
|
||||
SignedTransaction,
|
||||
} from '../../src/utils/types';
|
||||
import { chaiSetup } from '../utils/chai_setup';
|
||||
import { deployer } from '../utils/deployer';
|
||||
import { provider, web3Wrapper } from '../utils/web3_wrapper';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
|
||||
describe('Exchange transactions', () => {
|
||||
let senderAddress: string;
|
||||
let owner: string;
|
||||
let makerAddress: string;
|
||||
let takerAddress: string;
|
||||
let feeRecipientAddress: string;
|
||||
|
||||
let erc20TokenA: DummyERC20TokenContract;
|
||||
let erc20TokenB: DummyERC20TokenContract;
|
||||
let zrxToken: DummyERC20TokenContract;
|
||||
let exchange: ExchangeContract;
|
||||
let erc20Proxy: ERC20ProxyContract;
|
||||
|
||||
let erc20Balances: ERC20BalancesByOwner;
|
||||
let signedOrder: SignedOrder;
|
||||
let signedTx: SignedTransaction;
|
||||
let order: OrderStruct;
|
||||
let orderFactory: OrderFactory;
|
||||
let makerTransactionFactory: TransactionFactory;
|
||||
let takerTransactionFactory: TransactionFactory;
|
||||
let exchangeWrapper: ExchangeWrapper;
|
||||
let erc20Wrapper: ERC20Wrapper;
|
||||
|
||||
let defaultMakerTokenAddress: string;
|
||||
let defaultTakerTokenAddress: string;
|
||||
|
||||
let zeroEx: ZeroEx;
|
||||
|
||||
before(async () => {
|
||||
const accounts = await web3Wrapper.getAvailableAddressesAsync();
|
||||
const usedAddresses = ([owner, senderAddress, makerAddress, takerAddress, feeRecipientAddress] = accounts);
|
||||
|
||||
erc20Wrapper = new ERC20Wrapper(deployer, provider, usedAddresses, owner);
|
||||
|
||||
[erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
|
||||
erc20Proxy = await erc20Wrapper.deployProxyAsync();
|
||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||
|
||||
const exchangeInstance = await deployer.deployAsync(ContractName.Exchange, [
|
||||
assetProxyUtils.encodeERC20ProxyData(zrxToken.address),
|
||||
]);
|
||||
exchange = new ExchangeContract(exchangeInstance.abi, exchangeInstance.address, provider);
|
||||
zeroEx = new ZeroEx(provider, {
|
||||
exchangeContractAddress: exchange.address,
|
||||
networkId: constants.TESTRPC_NETWORK_ID,
|
||||
});
|
||||
exchangeWrapper = new ExchangeWrapper(exchange, zeroEx);
|
||||
await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner);
|
||||
|
||||
await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: owner });
|
||||
|
||||
defaultMakerTokenAddress = erc20TokenA.address;
|
||||
defaultTakerTokenAddress = erc20TokenB.address;
|
||||
|
||||
const defaultOrderParams = {
|
||||
...constants.STATIC_ORDER_PARAMS,
|
||||
senderAddress,
|
||||
exchangeAddress: exchange.address,
|
||||
makerAddress,
|
||||
feeRecipientAddress,
|
||||
makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultMakerTokenAddress),
|
||||
takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultTakerTokenAddress),
|
||||
};
|
||||
const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)];
|
||||
const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(takerAddress)];
|
||||
orderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams);
|
||||
makerTransactionFactory = new TransactionFactory(makerPrivateKey, exchange.address);
|
||||
takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchange.address);
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
|
||||
describe('executeTransaction', () => {
|
||||
describe('fillOrder', () => {
|
||||
let takerAssetFillAmount: BigNumber;
|
||||
beforeEach(async () => {
|
||||
erc20Balances = await erc20Wrapper.getBalancesAsync();
|
||||
signedOrder = orderFactory.newSignedOrder();
|
||||
order = orderUtils.getOrderStruct(signedOrder);
|
||||
|
||||
takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
|
||||
const data = exchange.fillOrder.getABIEncodedTransactionData(
|
||||
order,
|
||||
takerAssetFillAmount,
|
||||
signedOrder.signature,
|
||||
);
|
||||
signedTx = takerTransactionFactory.newSignedTransaction(data);
|
||||
});
|
||||
|
||||
it('should throw if not called by specified sender', async () => {
|
||||
return expect(exchangeWrapper.executeTransactionAsync(signedTx, takerAddress)).to.be.rejectedWith(
|
||||
constants.REVERT,
|
||||
);
|
||||
});
|
||||
|
||||
it('should transfer the correct amounts when signed by taker and called by sender', async () => {
|
||||
await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress);
|
||||
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)),
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if the a 0x transaction with the same transactionHash has already been executed', async () => {
|
||||
await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress);
|
||||
return expect(exchangeWrapper.executeTransactionAsync(signedTx, senderAddress)).to.be.rejectedWith(
|
||||
constants.REVERT,
|
||||
);
|
||||
});
|
||||
|
||||
it('should reset the currentContextAddress', async () => {
|
||||
await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress);
|
||||
const currentContextAddress = await exchange.currentContextAddress.callAsync();
|
||||
expect(currentContextAddress).to.equal(ZeroEx.NULL_ADDRESS);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cancelOrder', () => {
|
||||
beforeEach(async () => {
|
||||
const data = exchange.cancelOrder.getABIEncodedTransactionData(order);
|
||||
signedTx = makerTransactionFactory.newSignedTransaction(data);
|
||||
});
|
||||
|
||||
it('should throw if not called by specified sender', async () => {
|
||||
return expect(exchangeWrapper.executeTransactionAsync(signedTx, makerAddress)).to.be.rejectedWith(
|
||||
constants.REVERT,
|
||||
);
|
||||
});
|
||||
|
||||
it('should cancel the order when signed by maker and called by sender', async () => {
|
||||
await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress);
|
||||
const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress);
|
||||
const newBalances = await erc20Wrapper.getBalancesAsync();
|
||||
expect(newBalances).to.deep.equal(erc20Balances);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user