aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contracts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/contracts')
-rw-r--r--packages/contracts/src/utils/crypto.ts2
-rw-r--r--packages/contracts/src/utils/exchange_wrapper.ts16
-rw-r--r--packages/contracts/src/utils/order_factory.ts1
-rw-r--r--packages/contracts/src/utils/order_utils.ts3
-rw-r--r--packages/contracts/src/utils/transaction_factory.ts35
-rw-r--r--packages/contracts/src/utils/types.ts9
-rw-r--r--packages/contracts/test/exchange/transactions.ts161
7 files changed, 226 insertions, 1 deletions
diff --git a/packages/contracts/src/utils/crypto.ts b/packages/contracts/src/utils/crypto.ts
index 222cb7cca..85a37122a 100644
--- a/packages/contracts/src/utils/crypto.ts
+++ b/packages/contracts/src/utils/crypto.ts
@@ -29,6 +29,8 @@ export const crypto = {
args[i] = new BN(arg.toString(10), 10);
} else if (ethUtil.isValidAddress(arg)) {
argTypes.push('address');
+ } else if (arg instanceof Buffer) {
+ argTypes.push('bytes');
} else if (_.isString(arg)) {
argTypes.push('string');
} else if (_.isBuffer(arg)) {
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;
+}
diff --git a/packages/contracts/test/exchange/transactions.ts b/packages/contracts/test/exchange/transactions.ts
new file mode 100644
index 000000000..697fb70e2
--- /dev/null
+++ b/packages/contracts/test/exchange/transactions.ts
@@ -0,0 +1,161 @@
+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 order: OrderStruct;
+ let orderFactory: OrderFactory;
+ let transactionFactory: 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);
+ transactionFactory = new TransactionFactory(takerPrivateKey, exchange.address);
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+
+ describe('executeTransaction', () => {
+ describe('fillOrder', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ signedOrder = orderFactory.newSignedOrder();
+ order = orderUtils.getOrderStruct(signedOrder);
+ });
+
+ it('should transfer the correct amounts when signed by taker and called by sender', async () => {
+ const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
+ const data = exchange.fillOrder.getABIEncodedTransactionData(
+ order,
+ takerAssetFillAmount,
+ signedOrder.signature,
+ );
+ const signedTx = transactionFactory.newSignedTransaction(data);
+ const res = 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)),
+ );
+ });
+ });
+ });
+});