diff options
-rw-r--r-- | packages/contracts/src/utils/crypto.ts | 2 | ||||
-rw-r--r-- | packages/contracts/src/utils/exchange_wrapper.ts | 16 | ||||
-rw-r--r-- | packages/contracts/src/utils/order_factory.ts | 1 | ||||
-rw-r--r-- | packages/contracts/src/utils/order_utils.ts | 3 | ||||
-rw-r--r-- | packages/contracts/src/utils/transaction_factory.ts | 35 | ||||
-rw-r--r-- | packages/contracts/src/utils/types.ts | 9 | ||||
-rw-r--r-- | packages/contracts/test/exchange/transactions.ts | 161 |
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)), + ); + }); + }); + }); +}); |