diff options
Diffstat (limited to 'packages/contracts')
8 files changed, 225 insertions, 8 deletions
diff --git a/packages/contracts/compiler.json b/packages/contracts/compiler.json index 6ef8e6a95..a5cfa8761 100644 --- a/packages/contracts/compiler.json +++ b/packages/contracts/compiler.json @@ -26,6 +26,7 @@ "ERC20Proxy", "ERC721Proxy", "Exchange", + "ExchangeWrapper", "MixinAuthorizable", "MultiSigWallet", "MultiSigWalletWithTimeLock", diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 8807c727e..cf3f6f01d 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -33,7 +33,8 @@ "test:circleci": "yarn test" }, "config": { - "abis": "../migrations/artifacts/2.0.0/@(AssetProxyOwner|DummyERC20Token|DummyERC721Receiver|DummyERC721Token|ERC20Proxy|ERC721Proxy|Exchange|MixinAuthorizable|MultiSigWallet|MultiSigWalletWithTimeLock|TestAssetDataDecoders|TestAssetProxyDispatcher|TestLibBytes|TestLibMem|TestLibs|TestSignatureValidator|TokenRegistry|Whitelist|WETH9|ZRXToken).json" + "abis": + "../migrations/artifacts/2.0.0/@(AssetProxyOwner|DummyERC20Token|DummyERC721Receiver|DummyERC721Token|ERC20Proxy|ERC721Proxy|Exchange|ExchangeWrapper|MixinAuthorizable|MultiSigWallet|MultiSigWalletWithTimeLock|TestAssetDataDecoders|TestAssetProxyDispatcher|TestLibBytes|TestLibMem|TestLibs|TestSignatureValidator|TokenRegistry|Whitelist|WETH9|ZRXToken).json" }, "repository": { "type": "git", diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol index 0a0f0209a..2ebbfdabf 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol @@ -44,9 +44,9 @@ contract MixinExchangeCore is // Mapping of orderHash => cancelled mapping (bytes32 => bool) public cancelled; - // Mapping of makerAddress => lowest salt an order can have in order to be fillable + // Mapping of makerAddress => senderAddress => lowest salt an order can have in order to be fillable // Orders with a salt less than their maker's epoch are considered cancelled - mapping (address => uint256) public makerEpoch; + mapping (address => mapping (address => uint256)) public makerEpoch; ////// Core exchange functions ////// @@ -56,10 +56,13 @@ contract MixinExchangeCore is external { address makerAddress = getCurrentContextAddress(); + // If this function is called via `executeTransaction`, we only update the makerEpoch for the makerAddress/msg.sender combination. + // This allows external filter contracts to add rules to how orders are cancelled via this function. + address senderAddress = makerAddress == msg.sender ? address(0) : msg.sender; // makerEpoch is initialized to 0, so to cancelUpTo we need salt + 1 uint256 newMakerEpoch = salt + 1; - uint256 oldMakerEpoch = makerEpoch[makerAddress]; + uint256 oldMakerEpoch = makerEpoch[makerAddress][senderAddress]; // Ensure makerEpoch is monotonically increasing require( @@ -68,8 +71,8 @@ contract MixinExchangeCore is ); // Update makerEpoch - makerEpoch[makerAddress] = newMakerEpoch; - emit CancelUpTo(makerAddress, newMakerEpoch); + makerEpoch[makerAddress][senderAddress] = newMakerEpoch; + emit CancelUpTo(makerAddress, senderAddress, newMakerEpoch); } /// @dev Fills the input order. @@ -180,7 +183,7 @@ contract MixinExchangeCore is orderInfo.orderStatus = uint8(OrderStatus.CANCELLED); return orderInfo; } - if (makerEpoch[order.makerAddress] > order.salt) { + if (makerEpoch[order.makerAddress][order.senderAddress] > order.salt) { orderInfo.orderStatus = uint8(OrderStatus.CANCELLED); return orderInfo; } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol index de7c4d3af..b4552b7bf 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol @@ -52,6 +52,7 @@ contract MExchangeCore is // CancelUpTo event is emitted whenever `cancelOrdersUpTo` is executed succesfully. event CancelUpTo( address indexed makerAddress, + address indexed senderAddress, uint256 makerEpoch ); diff --git a/packages/contracts/src/contracts/current/test/ExchangeWrapper/ExchangeWrapper.sol b/packages/contracts/src/contracts/current/test/ExchangeWrapper/ExchangeWrapper.sol new file mode 100644 index 000000000..629a846d9 --- /dev/null +++ b/packages/contracts/src/contracts/current/test/ExchangeWrapper/ExchangeWrapper.sol @@ -0,0 +1,97 @@ +/* + + 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.24; +pragma experimental ABIEncoderV2; + +import "../../protocol/Exchange/interfaces/IExchange.sol"; +import "../../protocol/Exchange/libs/LibOrder.sol"; + +contract ExchangeWrapper { + + // Exchange contract. + IExchange EXCHANGE; + + constructor (address _exchange) + public + { + EXCHANGE = IExchange(_exchange); + } + + /// @dev Fills an order using `msg.sender` as the taker. + /// @param order Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param salt Arbitrary value to gaurantee uniqueness of 0x transaction hash. + /// @param orderSignature Proof that order has been created by maker. + /// @param takerSignature Proof that taker wishes to call this function with given params. + function fillOrder( + LibOrder.Order memory order, + uint256 takerAssetFillAmount, + uint256 salt, + bytes memory orderSignature, + bytes memory takerSignature + ) + public + { + address takerAddress = msg.sender; + + // Encode arguments into byte array. + bytes memory data = abi.encodeWithSelector( + EXCHANGE.fillOrder.selector, + order, + takerAssetFillAmount, + orderSignature + ); + + // Call `fillOrder` via `executeTransaction`. + EXCHANGE.executeTransaction( + salt, + takerAddress, + data, + takerSignature + ); + } + + /// @dev Cancels all orders created by sender with a salt less than or equal to the specified salt value. + /// @param cancelSalt Orders created with a salt less or equal to this value will be cancelled. + /// @param salt Arbitrary value to gaurantee uniqueness of 0x transaction hash. + /// @param makerSignature Proof that maker wishes to call this function with given params. + function cancelOrdersUpTo( + uint256 cancelSalt, + uint256 salt, + bytes makerSignature + ) + external + { + address makerAddress = msg.sender; + + // Encode arguments into byte array. + bytes memory data = abi.encodeWithSelector( + EXCHANGE.cancelOrdersUpTo.selector, + cancelSalt + ); + + // Call `cancelOrdersUpTo` via `executeTransaction`. + EXCHANGE.executeTransaction( + salt, + makerAddress, + data, + makerSignature + ); + } +} diff --git a/packages/contracts/src/contracts/current/test/Whitelist/Whitelist.sol b/packages/contracts/src/contracts/current/test/Whitelist/Whitelist.sol index 0594e2767..460c9ea42 100644 --- a/packages/contracts/src/contracts/current/test/Whitelist/Whitelist.sol +++ b/packages/contracts/src/contracts/current/test/Whitelist/Whitelist.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "../../protocol/Exchange/interfaces/IExchange.sol"; diff --git a/packages/contracts/src/utils/artifacts.ts b/packages/contracts/src/utils/artifacts.ts index bf7221d6d..7ba467708 100644 --- a/packages/contracts/src/utils/artifacts.ts +++ b/packages/contracts/src/utils/artifacts.ts @@ -7,6 +7,7 @@ import * as DummyERC721Token from '../artifacts/DummyERC721Token.json'; import * as ERC20Proxy from '../artifacts/ERC20Proxy.json'; import * as ERC721Proxy from '../artifacts/ERC721Proxy.json'; import * as Exchange from '../artifacts/Exchange.json'; +import * as ExchangeWrapper from '../artifacts/ExchangeWrapper.json'; import * as MixinAuthorizable from '../artifacts/MixinAuthorizable.json'; import * as MultiSigWallet from '../artifacts/MultiSigWallet.json'; import * as MultiSigWalletWithTimeLock from '../artifacts/MultiSigWalletWithTimeLock.json'; @@ -29,6 +30,7 @@ export const artifacts = { ERC20Proxy: (ERC20Proxy as any) as ContractArtifact, ERC721Proxy: (ERC721Proxy as any) as ContractArtifact, Exchange: (Exchange as any) as ContractArtifact, + ExchangeWrapper: (ExchangeWrapper as any) as ContractArtifact, EtherToken: (EtherToken as any) as ContractArtifact, MixinAuthorizable: (MixinAuthorizable as any) as ContractArtifact, MultiSigWallet: (MultiSigWallet as any) as ContractArtifact, diff --git a/packages/contracts/test/exchange/transactions.ts b/packages/contracts/test/exchange/transactions.ts index 21ff123e5..088b537cc 100644 --- a/packages/contracts/test/exchange/transactions.ts +++ b/packages/contracts/test/exchange/transactions.ts @@ -7,6 +7,7 @@ import * as chai from 'chai'; import { DummyERC20TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c20_token'; import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c20_proxy'; import { ExchangeContract } from '../../src/generated_contract_wrappers/exchange'; +import { ExchangeWrapperContract } from '../../src/generated_contract_wrappers/exchange_wrapper'; import { WhitelistContract } from '../../src/generated_contract_wrappers/whitelist'; import { artifacts } from '../../src/utils/artifacts'; import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions'; @@ -198,6 +199,117 @@ describe('Exchange transactions', () => { ); }); }); + + describe('cancelOrdersUpTo', () => { + let exchangeWrapperContract: ExchangeWrapperContract; + + before(async () => { + exchangeWrapperContract = await ExchangeWrapperContract.deployFrom0xArtifactAsync( + artifacts.ExchangeWrapper, + provider, + txDefaults, + exchange.address, + ); + }); + + it("should cancel an order if called from the order's sender", async () => { + const orderSalt = new BigNumber(0); + signedOrder = orderFactory.newSignedOrder({ + senderAddress: exchangeWrapperContract.address, + salt: orderSalt, + }); + const cancelSalt = orderSalt.add(1); + const cancelData = exchange.cancelOrdersUpTo.getABIEncodedTransactionData(cancelSalt); + const signedCancelTx = makerTransactionFactory.newSignedTransaction(cancelData); + await exchangeWrapperContract.cancelOrdersUpTo.sendTransactionAsync( + cancelSalt, + signedCancelTx.salt, + signedCancelTx.signature, + { + from: makerAddress, + }, + ); + + const takerAssetFillAmount = signedOrder.takerAssetAmount; + orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); + const fillData = exchange.fillOrder.getABIEncodedTransactionData( + orderWithoutExchangeAddress, + takerAssetFillAmount, + signedOrder.signature, + ); + const signedFillTx = takerTransactionFactory.newSignedTransaction(fillData); + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapperContract.fillOrder.sendTransactionAsync( + orderWithoutExchangeAddress, + takerAssetFillAmount, + signedFillTx.salt, + signedOrder.signature, + signedFillTx.signature, + { from: takerAddress }, + ), + ); + }); + + it("should not cancel an order if not called from the order's sender", async () => { + const orderSalt = new BigNumber(0); + signedOrder = orderFactory.newSignedOrder({ + senderAddress: exchangeWrapperContract.address, + salt: orderSalt, + }); + const cancelSalt = orderSalt.add(1); + await exchangeWrapper.cancelOrdersUpToAsync(cancelSalt, makerAddress); + + erc20Balances = await erc20Wrapper.getBalancesAsync(); + const takerAssetFillAmount = signedOrder.takerAssetAmount; + orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); + const data = exchange.fillOrder.getABIEncodedTransactionData( + orderWithoutExchangeAddress, + takerAssetFillAmount, + signedOrder.signature, + ); + signedTx = takerTransactionFactory.newSignedTransaction(data); + await exchangeWrapperContract.fillOrder.sendTransactionAsync( + orderWithoutExchangeAddress, + takerAssetFillAmount, + signedTx.salt, + signedOrder.signature, + signedTx.signature, + { from: takerAddress }, + ); + + 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)), + ); + }); + }); }); describe('Whitelist', () => { |