Merge pull request #498 from 0xProject/feature/contracts/takerAbstraction

Sender abstraction
This commit is contained in:
Amir Bandeali
2018-04-26 10:59:03 -07:00
committed by GitHub
12 changed files with 489 additions and 48 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

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