diff --git a/contracts/coordinator/test/coordinator.ts b/contracts/coordinator/test/coordinator.ts index c4c825a02f..aab9c9538f 100644 --- a/contracts/coordinator/test/coordinator.ts +++ b/contracts/coordinator/test/coordinator.ts @@ -135,7 +135,7 @@ describe('Coordinator tests', () => { it(`${fnName} should fill the order with a signed approval`, async () => { const orders = [await orderFactory.newSignedOrderAsync()]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER); const approval = approvalFactory.newSignedApproval( @@ -174,7 +174,7 @@ describe('Coordinator tests', () => { it(`${fnName} should fill the order if called by approver`, async () => { const orders = [await orderFactory.newSignedOrderAsync()]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const transactionReceipt = await web3Wrapper.awaitTransactionSuccessAsync( await coordinatorContract.executeTransaction.sendTransactionAsync( transaction, @@ -206,7 +206,7 @@ describe('Coordinator tests', () => { it(`${fnName} should revert with no approval signature`, async () => { const orders = [await orderFactory.newSignedOrderAsync()]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); await expectTransactionFailedAsync( coordinatorContract.executeTransaction.sendTransactionAsync( transaction, @@ -225,7 +225,7 @@ describe('Coordinator tests', () => { it(`${fnName} should revert with an invalid approval signature`, async () => { const orders = [await orderFactory.newSignedOrderAsync()]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER); const approval = approvalFactory.newSignedApproval( @@ -249,7 +249,7 @@ describe('Coordinator tests', () => { it(`${fnName} should revert with an expired approval`, async () => { const orders = [await orderFactory.newSignedOrderAsync()]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).minus(constants.TIME_BUFFER); const approval = approvalFactory.newSignedApproval( @@ -272,7 +272,7 @@ describe('Coordinator tests', () => { it(`${fnName} should revert if not called by tx signer or approver`, async () => { const orders = [await orderFactory.newSignedOrderAsync()]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER); const approval = approvalFactory.newSignedApproval( @@ -299,7 +299,7 @@ describe('Coordinator tests', () => { it(`${fnName} should fill the orders with a signed approval`, async () => { const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER); const approval = approvalFactory.newSignedApproval( @@ -340,7 +340,7 @@ describe('Coordinator tests', () => { it(`${fnName} should fill the orders if called by approver`, async () => { const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const transactionReceipt = await web3Wrapper.awaitTransactionSuccessAsync( await coordinatorContract.executeTransaction.sendTransactionAsync( transaction, @@ -374,7 +374,7 @@ describe('Coordinator tests', () => { it(`${fnName} should revert with an invalid approval signature`, async () => { const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER); const approval = approvalFactory.newSignedApproval( @@ -398,7 +398,7 @@ describe('Coordinator tests', () => { it(`${fnName} should revert with an expired approval`, async () => { const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).minus(constants.TIME_BUFFER); const approval = approvalFactory.newSignedApproval( @@ -421,7 +421,7 @@ describe('Coordinator tests', () => { it(`${fnName} should revert if not called by tx signer or approver`, async () => { const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER); const approval = approvalFactory.newSignedApproval( @@ -447,7 +447,7 @@ describe('Coordinator tests', () => { it('cancelOrder call should be successful without an approval', async () => { const orders = [await orderFactory.newSignedOrderAsync()]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, orders); - const transaction = makerTransactionFactory.newSignedTransaction(data); + const transaction = await makerTransactionFactory.newSignedTransactionAsync({ data }); const transactionReceipt = await web3Wrapper.awaitTransactionSuccessAsync( await coordinatorContract.executeTransaction.sendTransactionAsync( transaction, @@ -475,7 +475,7 @@ describe('Coordinator tests', () => { it('batchCancelOrders call should be successful without an approval', async () => { const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.BatchCancelOrders, orders); - const transaction = makerTransactionFactory.newSignedTransaction(data); + const transaction = await makerTransactionFactory.newSignedTransactionAsync({ data }); const transactionReceipt = await web3Wrapper.awaitTransactionSuccessAsync( await coordinatorContract.executeTransaction.sendTransactionAsync( transaction, @@ -505,7 +505,7 @@ describe('Coordinator tests', () => { it('cancelOrdersUpTo call should be successful without an approval', async () => { const targetEpoch = constants.ZERO_AMOUNT; const data = exchange.cancelOrdersUpTo.getABIEncodedTransactionData(targetEpoch); - const transaction = makerTransactionFactory.newSignedTransaction(data); + const transaction = await makerTransactionFactory.newSignedTransactionAsync({ data }); const transactionReceipt = await web3Wrapper.awaitTransactionSuccessAsync( await coordinatorContract.executeTransaction.sendTransactionAsync( transaction, diff --git a/contracts/coordinator/test/libs.ts b/contracts/coordinator/test/libs.ts index a3cbcf3106..a594cbd58c 100644 --- a/contracts/coordinator/test/libs.ts +++ b/contracts/coordinator/test/libs.ts @@ -42,6 +42,7 @@ describe('Libs tests', () => { it('should return the correct transaction hash', async () => { const tx = { salt: new BigNumber(0), + expirationTimeSeconds: new BigNumber(0), signerAddress: constants.NULL_ADDRESS, data: '0x1234', domain: { @@ -59,6 +60,7 @@ describe('Libs tests', () => { it('should return the correct approval hash', async () => { const signedTx = { salt: new BigNumber(0), + expirationTimeSeconds: new BigNumber(0), signerAddress: constants.NULL_ADDRESS, data: '0x1234', signature: '0x5678', diff --git a/contracts/coordinator/test/mixins.ts b/contracts/coordinator/test/mixins.ts index e35a74d5dc..96292df260 100644 --- a/contracts/coordinator/test/mixins.ts +++ b/contracts/coordinator/test/mixins.ts @@ -92,21 +92,21 @@ describe('Mixins tests', () => { describe('getSignerAddress', () => { it('should return the correct address using the EthSign signature type', async () => { const data = constants.NULL_BYTES; - const transaction = transactionFactory.newSignedTransaction(data, SignatureType.EthSign); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }, SignatureType.EthSign); const transactionHash = transactionHashUtils.getTransactionHashHex(transaction); const signerAddress = await mixins.getSignerAddress.callAsync(transactionHash, transaction.signature); expect(transaction.signerAddress).to.eq(signerAddress); }); it('should return the correct address using the EIP712 signature type', async () => { const data = constants.NULL_BYTES; - const transaction = transactionFactory.newSignedTransaction(data, SignatureType.EIP712); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }, SignatureType.EIP712); const transactionHash = transactionHashUtils.getTransactionHashHex(transaction); const signerAddress = await mixins.getSignerAddress.callAsync(transactionHash, transaction.signature); expect(transaction.signerAddress).to.eq(signerAddress); }); it('should revert with with the Illegal signature type', async () => { const data = constants.NULL_BYTES; - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); const illegalSignatureByte = ethUtil.toBuffer(SignatureType.Illegal).toString('hex'); transaction.signature = `${transaction.signature.slice( 0, @@ -120,7 +120,7 @@ describe('Mixins tests', () => { }); it('should revert with with the Invalid signature type', async () => { const data = constants.NULL_BYTES; - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); const invalidSignatureByte = ethUtil.toBuffer(SignatureType.Invalid).toString('hex'); transaction.signature = `0x${invalidSignatureByte}`; const transactionHash = transactionHashUtils.getTransactionHashHex(transaction); @@ -131,7 +131,7 @@ describe('Mixins tests', () => { }); it("should revert with with a signature type that doesn't exist", async () => { const data = constants.NULL_BYTES; - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); const invalidSignatureByte = '04'; transaction.signature = `${transaction.signature.slice( 0, @@ -218,7 +218,7 @@ describe('Mixins tests', () => { it(`Should be successful: function=${fnName}, caller=tx_signer, senderAddress=[verifier], approval_sig=[approver1], expiration=[valid]`, async () => { const orders = [defaultOrder]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER); const approval = approvalFactory1.newSignedApproval( @@ -242,7 +242,7 @@ describe('Mixins tests', () => { }; const orders = [order]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER); const approval = approvalFactory1.newSignedApproval( @@ -262,7 +262,7 @@ describe('Mixins tests', () => { it(`Should be successful: function=${fnName}, caller=approver1, senderAddress=[verifier], approval_sig=[], expiration=[]`, async () => { const orders = [defaultOrder]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); await mixins.assertValidCoordinatorApprovals.callAsync( transaction, approvalSignerAddress1, @@ -277,7 +277,7 @@ describe('Mixins tests', () => { it(`Should be successful: function=${fnName}, caller=approver1, senderAddress=[verifier], approval_sig=[approver1], expiration=[invalid]`, async () => { const orders = [defaultOrder]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER); const approval = approvalFactory1.newSignedApproval( @@ -297,7 +297,7 @@ describe('Mixins tests', () => { it(`Should be successful: function=${fnName}, caller=approver1, senderAddress=[verifier], approval_sig=[], expiration=[]`, async () => { const orders = [defaultOrder]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); await mixins.assertValidCoordinatorApprovals.callAsync( transaction, approvalSignerAddress1, @@ -312,7 +312,7 @@ describe('Mixins tests', () => { it(`Should revert: function=${fnName}, caller=tx_signer, senderAddress=[verifier], approval_sig=[invalid], expiration=[valid]`, async () => { const orders = [defaultOrder]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER); const approval = approvalFactory1.newSignedApproval( @@ -336,7 +336,7 @@ describe('Mixins tests', () => { it(`Should revert: function=${fnName}, caller=tx_signer, senderAddress=[verifier], approval_sig=[approver1], expiration=[invalid]`, async () => { const orders = [defaultOrder]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).minus(constants.TIME_BUFFER); const approval = approvalFactory1.newSignedApproval( @@ -359,7 +359,7 @@ describe('Mixins tests', () => { it(`Should revert: function=${fnName}, caller=approver2, senderAddress=[verifier], approval_sig=[approver1], expiration=[valid]`, async () => { const orders = [defaultOrder]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER); const approval = approvalFactory1.newSignedApproval( @@ -390,7 +390,7 @@ describe('Mixins tests', () => { it(`Should be successful: function=${fnName} caller=tx_signer, senderAddress=[verifier,verifier], feeRecipient=[approver1,approver1], approval_sig=[approver1], expiration=[valid]`, async () => { const orders = [defaultOrder, defaultOrder]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER); const approval = approvalFactory1.newSignedApproval( @@ -413,7 +413,7 @@ describe('Mixins tests', () => { senderAddress: constants.NULL_ADDRESS, })); const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER); const approval = approvalFactory1.newSignedApproval( @@ -436,7 +436,7 @@ describe('Mixins tests', () => { senderAddress: constants.NULL_ADDRESS, })); const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); await mixins.assertValidCoordinatorApprovals.callAsync( transaction, transactionSignerAddress, @@ -449,7 +449,7 @@ describe('Mixins tests', () => { it(`Should be successful: function=${fnName} caller=tx_signer, senderAddress=[verifier,null], feeRecipient=[approver1,approver1], approval_sig=[approver1], expiration=[valid]`, async () => { const orders = [defaultOrder, { ...defaultOrder, senderAddress: constants.NULL_ADDRESS }]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER); const approval = approvalFactory1.newSignedApproval( @@ -469,7 +469,7 @@ describe('Mixins tests', () => { it(`Should be successful: function=${fnName} caller=tx_signer, senderAddress=[verifier,verifier], feeRecipient=[approver1,approver2], approval_sig=[approver1,approver2], expiration=[valid,valid]`, async () => { const orders = [defaultOrder, { ...defaultOrder, feeRecipientAddress: approvalSignerAddress2 }]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER); const approval1 = approvalFactory1.newSignedApproval( @@ -494,7 +494,7 @@ describe('Mixins tests', () => { it(`Should be successful: function=${fnName} caller=approver1, senderAddress=[verifier,verifier], feeRecipient=[approver1,approver1], approval_sig=[], expiration=[]`, async () => { const orders = [defaultOrder, defaultOrder]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); await mixins.assertValidCoordinatorApprovals.callAsync( transaction, approvalSignerAddress1, @@ -507,7 +507,7 @@ describe('Mixins tests', () => { it(`Should revert: function=${fnName} caller=approver1, senderAddress=[verifier,verifier], feeRecipient=[approver1,approver2], approval_sig=[approver2], expiration=[valid]`, async () => { const orders = [defaultOrder, { ...defaultOrder, feeRecipientAddress: approvalSignerAddress2 }]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER); const approval2 = approvalFactory2.newSignedApproval( @@ -530,7 +530,7 @@ describe('Mixins tests', () => { it(`Should revert: function=${fnName} caller=tx_signer, senderAddress=[verifier,verifier], feeRecipient=[approver1, approver1], approval_sig=[], expiration=[]`, async () => { const orders = [defaultOrder, defaultOrder]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); expectContractCallFailedAsync( mixins.assertValidCoordinatorApprovals.callAsync( transaction, @@ -546,7 +546,7 @@ describe('Mixins tests', () => { it(`Should revert: function=${fnName} caller=tx_signer, senderAddress=[verifier,verifier], feeRecipient=[approver1, approver1], approval_sig=[invalid], expiration=[valid]`, async () => { const orders = [defaultOrder, defaultOrder]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER); const approval = approvalFactory1.newSignedApproval( @@ -570,7 +570,7 @@ describe('Mixins tests', () => { it(`Should revert: function=${fnName} caller=tx_signer, senderAddress=[verifier,verifier], feeRecipient=[approver1, approver2], approval_sig=[valid,invalid], expiration=[valid,valid]`, async () => { const orders = [defaultOrder, { ...defaultOrder, feeRecipientAddress: approvalSignerAddress2 }]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER); const approval1 = approvalFactory1.newSignedApproval( @@ -599,7 +599,7 @@ describe('Mixins tests', () => { it(`Should revert: function=${fnName} caller=approver1, senderAddress=[verifier,verifier], feeRecipient=[approver1, approver2], approval_sig=[invalid], expiration=[valid]`, async () => { const orders = [defaultOrder, { ...defaultOrder, feeRecipientAddress: approvalSignerAddress2 }]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER); const approval2 = approvalFactory2.newSignedApproval( @@ -623,7 +623,7 @@ describe('Mixins tests', () => { it(`Should revert: function=${fnName} caller=tx_signer, senderAddress=[verifier,verifier], feeRecipient=[approver1, approver2], approval_sig=[valid,valid], expiration=[valid,invalid]`, async () => { const orders = [defaultOrder, { ...defaultOrder, feeRecipientAddress: approvalSignerAddress2 }]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds1 = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER); const approvalExpirationTimeSeconds2 = new BigNumber(currentTimestamp).minus(constants.TIME_BUFFER); @@ -652,7 +652,7 @@ describe('Mixins tests', () => { it(`Should revert: function=${fnName} caller=approver1, senderAddress=[verifier,verifier], feeRecipient=[approver1, approver2], approval_sig=[valid], expiration=[invalid]`, async () => { const orders = [defaultOrder, { ...defaultOrder, feeRecipientAddress: approvalSignerAddress2 }]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).minus(constants.TIME_BUFFER); const approval2 = approvalFactory2.newSignedApproval( @@ -675,7 +675,7 @@ describe('Mixins tests', () => { it(`Should revert: function=${fnName} caller=approver2, senderAddress=[verifier,verifier], feeRecipient=[approver1, approver1], approval_sig=[valid], expiration=[valid]`, async () => { const orders = [defaultOrder, defaultOrder]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); const currentTimestamp = await getLatestBlockTimestampAsync(); const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER); const approval1 = approvalFactory1.newSignedApproval( @@ -701,7 +701,7 @@ describe('Mixins tests', () => { it('should allow the tx signer to call `cancelOrder` without approval', async () => { const orders = [defaultOrder]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); await mixins.assertValidCoordinatorApprovals.callAsync( transaction, transactionSignerAddress, @@ -714,7 +714,7 @@ describe('Mixins tests', () => { it('should allow the tx signer to call `batchCancelOrders` without approval', async () => { const orders = [defaultOrder, defaultOrder]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.BatchCancelOrders, orders); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); await mixins.assertValidCoordinatorApprovals.callAsync( transaction, transactionSignerAddress, @@ -726,7 +726,7 @@ describe('Mixins tests', () => { }); it('should allow the tx signer to call `cancelOrdersUpTo` without approval', async () => { const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrdersUpTo); - const transaction = transactionFactory.newSignedTransaction(data); + const transaction = await transactionFactory.newSignedTransactionAsync({ data }); await mixins.assertValidCoordinatorApprovals.callAsync( transaction, transactionSignerAddress, diff --git a/contracts/exchange-libs/CHANGELOG.json b/contracts/exchange-libs/CHANGELOG.json index a89952ced9..80269bb508 100644 --- a/contracts/exchange-libs/CHANGELOG.json +++ b/contracts/exchange-libs/CHANGELOG.json @@ -49,6 +49,10 @@ { "note": "Add `generate-exchange-selectors` package script.", "pr": 1819 + }, + { + "note": "Add `expirationTimeSeconds` to `ZeroExTransaction` struct", + "pr": 1823 } ] }, diff --git a/contracts/exchange-libs/contracts/src/LibZeroExTransaction.sol b/contracts/exchange-libs/contracts/src/LibZeroExTransaction.sol index 5de8010532..7d32e7d758 100644 --- a/contracts/exchange-libs/contracts/src/LibZeroExTransaction.sol +++ b/contracts/exchange-libs/contracts/src/LibZeroExTransaction.sol @@ -29,16 +29,18 @@ contract LibZeroExTransaction is // keccak256(abi.encodePacked( // "ZeroExTransaction(", // "uint256 salt,", + // "uint256 expirationTimeSeconds," // "address signerAddress,", // "bytes data", // ")" // )); - bytes32 constant internal EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH = 0x213c6f636f3ea94e701c0adf9b2624aa45a6c694f9a292c094f9a81c24b5df4c; + bytes32 constant public EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH = 0x6b4c70d217b44d0ff0d3bf7aeb18eb8604c5cd06f615a4b497aeefa4f01d2775; struct ZeroExTransaction { - uint256 salt; // Arbitrary number to ensure uniqueness of transaction hash. - address signerAddress; // Address of transaction signer. - bytes data; // AbiV2 encoded calldata. + uint256 salt; // Arbitrary number to ensure uniqueness of transaction hash. + uint256 expirationTimeSeconds; // Timestamp in seconds at which transaction expires. + address signerAddress; // Address of transaction signer. + bytes data; // AbiV2 encoded calldata. } /// @dev Calculates the EIP712 hash of a 0x transaction using the domain separator of the Exchange contract. @@ -65,12 +67,14 @@ contract LibZeroExTransaction is bytes32 schemaHash = EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH; bytes memory data = transaction.data; uint256 salt = transaction.salt; + uint256 expirationTimeSeconds = transaction.expirationTimeSeconds; address signerAddress = transaction.signerAddress; // Assembly for more efficiently computing: // keccak256(abi.encodePacked( // EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH, // transaction.salt, + // transaction.expirationTimeSeconds, // uint256(transaction.signerAddress), // keccak256(transaction.data) // )); @@ -84,11 +88,12 @@ contract LibZeroExTransaction is mstore(memPtr, schemaHash) // hash of schema mstore(add(memPtr, 32), salt) // salt - mstore(add(memPtr, 64), and(signerAddress, 0xffffffffffffffffffffffffffffffffffffffff)) // signerAddress - mstore(add(memPtr, 96), dataHash) // hash of data + mstore(add(memPtr, 64), expirationTimeSeconds) // expirationTimeSeconds + mstore(add(memPtr, 96), and(signerAddress, 0xffffffffffffffffffffffffffffffffffffffff)) // signerAddress + mstore(add(memPtr, 128), dataHash) // hash of data // Compute hash - result := keccak256(memPtr, 128) + result := keccak256(memPtr, 160) } return result; } diff --git a/contracts/exchange-libs/contracts/test/TestLibs.sol b/contracts/exchange-libs/contracts/test/TestLibs.sol index 8548e0fbdd..b27be8af8d 100644 --- a/contracts/exchange-libs/contracts/test/TestLibs.sol +++ b/contracts/exchange-libs/contracts/test/TestLibs.sol @@ -22,6 +22,7 @@ pragma experimental ABIEncoderV2; import "../src/LibEIP712ExchangeDomain.sol"; import "../src/LibMath.sol"; import "../src/LibOrder.sol"; +import "../src/LibZeroExTransaction.sol"; import "../src/LibFillResults.sol"; @@ -30,6 +31,7 @@ contract TestLibs is LibEIP712ExchangeDomain, LibMath, LibOrder, + LibZeroExTransaction, LibFillResults { constructor (uint256 chainId) diff --git a/contracts/exchange-libs/test/exchange_libs.ts b/contracts/exchange-libs/test/exchange_libs.ts index 4d9b125115..38704c6704 100644 --- a/contracts/exchange-libs/test/exchange_libs.ts +++ b/contracts/exchange-libs/test/exchange_libs.ts @@ -1,21 +1,13 @@ -import { - addressUtils, - chaiSetup, - constants, - OrderFactory, - provider, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; +import { addressUtils, chaiSetup, constants, provider, txDefaults, web3Wrapper } from '@0x/contracts-test-utils'; import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; +import { assetDataUtils, orderHashUtils, transactionHashUtils } from '@0x/order-utils'; import { constants as orderConstants } from '@0x/order-utils/lib/src/constants'; -import { SignedOrder } from '@0x/types'; +import { Order, ZeroExTransaction } from '@0x/types'; import { BigNumber, providerUtils } from '@0x/utils'; import * as chai from 'chai'; import * as ethUtil from 'ethereumjs-util'; -import { TestLibsContract } from '../generated-wrappers/test_libs'; +import { TestLibsContract } from '../src'; import { artifacts } from '../src/artifacts'; import { stringifySchema } from './utils'; @@ -27,8 +19,8 @@ const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); describe('Exchange libs', () => { let chainId: number; - let signedOrder: SignedOrder; - let orderFactory: OrderFactory; + let order: Order; + let transaction: ZeroExTransaction; let libs: TestLibsContract; let libsAlternateChain: TestLibsContract; @@ -40,7 +32,7 @@ describe('Exchange libs', () => { }); before(async () => { const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const makerAddress = accounts[0]; + const [makerAddress, takerAddress, senderAddress, feeRecipientAddress, signerAddress] = accounts.slice(0, 5); chainId = await providerUtils.getChainIdAsync(provider); libs = await TestLibsContract.deployFrom0xArtifactAsync( artifacts.TestLibs, @@ -56,22 +48,31 @@ describe('Exchange libs', () => { txDefaults, new BigNumber(alternateChainId), ); - - const defaultOrderParams = { + const domain = { + verifyingContractAddress: libs.address, + chainId, + }; + order = { ...constants.STATIC_ORDER_PARAMS, makerAddress, - feeRecipientAddress: addressUtils.generatePseudoRandomAddress(), + takerAddress, + senderAddress, + feeRecipientAddress, makerAssetData: assetDataUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()), takerAssetData: assetDataUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()), makerFeeAssetData: assetDataUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()), takerFeeAssetData: assetDataUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()), - domain: { - verifyingContractAddress: libs.address, - chainId, - }, + salt: new BigNumber(0), + expirationTimeSeconds: new BigNumber(0), + domain, + }; + transaction = { + signerAddress, + salt: new BigNumber(0), + expirationTimeSeconds: new BigNumber(0), + data: constants.NULL_BYTES, + domain, }; - const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; - orderFactory = new OrderFactory(privateKey, defaultOrderParams); }); beforeEach(async () => { @@ -140,20 +141,42 @@ describe('Exchange libs', () => { describe('LibOrder', () => { describe('getOrderHash', () => { - it('should output the correct orderHash', async () => { - signedOrder = await orderFactory.newSignedOrderAsync(); - const orderHashHex = await libs.getOrderHash.callAsync(signedOrder); - expect(orderHashUtils.getOrderHashHex(signedOrder)).to.be.equal(orderHashHex); + it('should return the correct orderHash', async () => { + const orderHashHex = await libs.getOrderHash.callAsync(order); + expect(orderHashUtils.getOrderHashHex(order)).to.be.equal(orderHashHex); }); it('orderHash should differ if chainId is different', async () => { - signedOrder = await orderFactory.newSignedOrderAsync(); - const orderHashHex1 = await libsAlternateChain.getOrderHash.callAsync(signedOrder); - const orderHashHex2 = await libs.getOrderHash.callAsync(signedOrder); + const orderHashHex1 = await libsAlternateChain.getOrderHash.callAsync(order); + const orderHashHex2 = await libs.getOrderHash.callAsync(order); expect(orderHashHex1).to.be.not.equal(orderHashHex2); }); }); }); + describe('LibZeroExTransaction', () => { + describe('EIP712ZeroExTransactionSchemaHash', () => { + it('should return the correct schema hash', async () => { + const schemaHash = await libs.EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH.callAsync(); + const schemaString = + 'ZeroExTransaction(uint256 salt,uint256 expirationTimeSeconds,address signerAddress,bytes data)'; + const expectedSchemaHash = ethUtil.addHexPrefix(ethUtil.bufferToHex(ethUtil.sha3(schemaString))); + expect(schemaHash).to.equal(expectedSchemaHash); + }); + }); + describe('getTransactionHash', () => { + it('should return the correct transactionHash', async () => { + const transactionHash = await libs.getTransactionHash.callAsync(transaction); + const expectedTransactionHash = transactionHashUtils.getTransactionHashHex(transaction); + expect(transactionHash).to.equal(expectedTransactionHash); + }); + it('transactionHash should differ if chainId is different', async () => { + const transactionHash1 = await libsAlternateChain.getTransactionHash.callAsync(transaction); + const transactionHash2 = await libs.getTransactionHash.callAsync(transaction); + expect(transactionHash1).to.not.equal(transactionHash2); + }); + }); + }); + describe('LibEIP712', () => { it('should return the correct domain separator schema hash', async () => { const schema = stringifySchema(orderConstants.DEFAULT_DOMAIN_SCHEMA); diff --git a/contracts/exchange/CHANGELOG.json b/contracts/exchange/CHANGELOG.json index a50b380d6f..dfbd357441 100644 --- a/contracts/exchange/CHANGELOG.json +++ b/contracts/exchange/CHANGELOG.json @@ -30,6 +30,18 @@ "note": "Add support for `SignatureType.WalletOrderValidator` for orders", "pr": 1774 }, + { + "note": "Add a `bytes` return value to `executeTransaction`, which is equal to the encoded return data of the underlying Exchange function call", + "pr": 1793 + }, + { + "note": "Implement `batchExecuteTransactions`", + "pr": 1793 + }, + { + "note": "Refactor preSign to be compatible with `executeTransaction`", + "pr": 1793 + }, { "note": "Remove ZRX fees in lieu of arbitrary maker and taker fee tokens.", "pr": 1819 @@ -45,6 +57,18 @@ { "note": "Avoid redundant transfer in `fillOrder()` and `matchOrders()` when maker/taker is the same as feeRecipient and assets are the same", "pr": 1819 + }, + { + "note": "Implement `cancelOrderNoThrow` and `batchCancelOrdersNoThrow` functions", + "pr": 1827 + }, + { + "note": "`executeTransaction` will now revert if the input transaction is expired", + "pr": 1832 + }, + { + "note": "Log an `TransactionExecuted` event when an `executeTransaction` call is successful", + "pr": 1832 } ] }, diff --git a/contracts/exchange/contracts/examples/ExchangeWrapper.sol b/contracts/exchange/contracts/examples/ExchangeWrapper.sol index 7aa6bac216..c3b21b8e3f 100644 --- a/contracts/exchange/contracts/examples/ExchangeWrapper.sol +++ b/contracts/exchange/contracts/examples/ExchangeWrapper.sol @@ -40,10 +40,12 @@ contract ExchangeWrapper { /// and senderAddress equal to this contract. /// @param targetOrderEpoch 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 transactionExpirationTimeSeconds Timestamp in seconds ar which ZeroExTransaction expires. /// @param makerSignature Proof that maker wishes to call this function with given params. function cancelOrdersUpTo( uint256 targetOrderEpoch, uint256 salt, + uint256 transactionExpirationTimeSeconds, bytes calldata makerSignature ) external @@ -58,6 +60,7 @@ contract ExchangeWrapper { LibZeroExTransaction.ZeroExTransaction memory transaction = LibZeroExTransaction.ZeroExTransaction({ salt: salt, + expirationTimeSeconds: transactionExpirationTimeSeconds, data: data, signerAddress: makerAddress }); @@ -70,12 +73,14 @@ contract ExchangeWrapper { /// @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 transactionExpirationTimeSeconds Timestamp in seconds ar which ZeroExTransaction expires. /// @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, + uint256 transactionExpirationTimeSeconds, bytes memory orderSignature, bytes memory takerSignature ) @@ -93,6 +98,7 @@ contract ExchangeWrapper { LibZeroExTransaction.ZeroExTransaction memory transaction = LibZeroExTransaction.ZeroExTransaction({ salt: salt, + expirationTimeSeconds: transactionExpirationTimeSeconds, data: data, signerAddress: takerAddress }); diff --git a/contracts/exchange/contracts/examples/Whitelist.sol b/contracts/exchange/contracts/examples/Whitelist.sol index 16d4c8df11..4cdc389c95 100644 --- a/contracts/exchange/contracts/examples/Whitelist.sol +++ b/contracts/exchange/contracts/examples/Whitelist.sol @@ -28,7 +28,6 @@ import "@0x/contracts-utils/contracts/src/Ownable.sol"; contract Whitelist is Ownable { - // Mapping of address => whitelist status. mapping (address => bool) public isWhitelisted; @@ -129,6 +128,7 @@ contract Whitelist is LibZeroExTransaction.ZeroExTransaction memory transaction = LibZeroExTransaction.ZeroExTransaction({ salt: salt, data: data, + expirationTimeSeconds: uint256(-1), signerAddress: takerAddress }); diff --git a/contracts/exchange/contracts/src/MixinTransactions.sol b/contracts/exchange/contracts/src/MixinTransactions.sol index fa83949fe0..8cbede3fc1 100644 --- a/contracts/exchange/contracts/src/MixinTransactions.sol +++ b/contracts/exchange/contracts/src/MixinTransactions.sol @@ -85,6 +85,15 @@ contract MixinTransactions is { bytes32 transactionHash = getTransactionHash(transaction); + // Check transaction is not expired + // solhint-disable-next-line not-rely-on-time + if (block.timestamp >= transaction.expirationTimeSeconds) { + _rrevert(TransactionError( + TransactionErrorCodes.EXPIRED, + transactionHash + )); + } + // Prevent reentrancy if (currentContextAddress != address(0)) { _rrevert(TransactionError( @@ -135,6 +144,8 @@ contract MixinTransactions is currentContextAddress = address(0); } + emit TransactionExecution(transactionHash); + return returnData; } diff --git a/contracts/exchange/contracts/src/mixins/MExchangeRichErrorTypes.sol b/contracts/exchange/contracts/src/mixins/MExchangeRichErrorTypes.sol index f8c0c46976..26d7ff0e9f 100644 --- a/contracts/exchange/contracts/src/mixins/MExchangeRichErrorTypes.sol +++ b/contracts/exchange/contracts/src/mixins/MExchangeRichErrorTypes.sol @@ -47,7 +47,8 @@ contract MExchangeRichErrorTypes is enum TransactionErrorCodes { NO_REENTRANCY, - ALREADY_EXECUTED + ALREADY_EXECUTED, + EXPIRED } // bytes4(keccak256("SignatureError(uint8,bytes32,address,bytes)")) diff --git a/contracts/exchange/contracts/src/mixins/MTransactions.sol b/contracts/exchange/contracts/src/mixins/MTransactions.sol index 9a8f342a14..9f6d6ffef9 100644 --- a/contracts/exchange/contracts/src/mixins/MTransactions.sol +++ b/contracts/exchange/contracts/src/mixins/MTransactions.sol @@ -26,6 +26,9 @@ import "../interfaces/ITransactions.sol"; contract MTransactions is ITransactions { + // TransactionExecution event is emitted when a ZeroExTransaction is executed. + event TransactionExecution(bytes32 indexed transactionHash); + /// @dev Executes an Exchange method call in the context of signer. /// @param transaction 0x transaction containing salt, signerAddress, and data. /// @param signature Proof that transaction has been signed by signer. diff --git a/contracts/exchange/test/transactions.ts b/contracts/exchange/test/transactions.ts index fc99cb8ed2..63cdca60e7 100644 --- a/contracts/exchange/test/transactions.ts +++ b/contracts/exchange/test/transactions.ts @@ -5,6 +5,7 @@ import { chaiSetup, constants, FillResults, + getLatestBlockTimestampAsync, LogDecoder, OrderFactory, provider, @@ -37,6 +38,7 @@ import { ExchangeFillEventArgs, ExchangeFunctionName, ExchangeSignatureValidatorApprovalEventArgs, + ExchangeTransactionExecutionEventArgs, ExchangeWrapper, ExchangeWrapperContract, WhitelistContract, @@ -157,6 +159,44 @@ describe('Exchange transactions', () => { taker2TransactionFactory = new TransactionFactory(taker2PrivateKey, exchangeInstance.address, chainId); }); describe('executeTransaction', () => { + describe('general functionality', () => { + it('should log the correct transactionHash if successfully executed', async () => { + const order = await orderFactory.newSignedOrderAsync(); + const orders = [order]; + const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, orders); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); + const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, takerAddress); + const transactionExecutionLogs = transactionReceipt.logs.filter( + log => + (log as LogWithDecodedArgs).event === + 'TransactionExecution', + ); + expect(transactionExecutionLogs.length).to.eq(1); + const executionLogArgs = (transactionExecutionLogs[0] as LogWithDecodedArgs< + ExchangeTransactionExecutionEventArgs + >).args; + expect(executionLogArgs.transactionHash).to.equal( + transactionHashUtils.getTransactionHashHex(transaction), + ); + }); + it('should revert if the transaction is expired', async () => { + const order = await orderFactory.newSignedOrderAsync(); + const orders = [order]; + const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, orders); + const currentTimestamp = await getLatestBlockTimestampAsync(); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ + data, + expirationTimeSeconds: new BigNumber(currentTimestamp).minus(10), + }); + const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); + const expectedError = new ExchangeRevertErrors.TransactionError( + ExchangeRevertErrors.TransactionErrorCode.Expired, + transactionHashHex, + ); + const tx = exchangeWrapper.executeTransactionAsync(transaction, senderAddress); + return expect(tx).to.revertWith(expectedError); + }); + }); describe('fill methods', () => { for (const fnName of [ ...exchangeConstants.SINGLE_FILL_FN_NAMES, @@ -166,7 +206,7 @@ describe('Exchange transactions', () => { it(`${fnName} should revert if signature is invalid and not called by signer`, async () => { const orders = [await orderFactory.newSignedOrderAsync()]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const v = ethUtil.toBuffer(transaction.signature.slice(0, 4)); const invalidR = ethUtil.sha3('invalidR'); const invalidS = ethUtil.sha3('invalidS'); @@ -186,7 +226,7 @@ describe('Exchange transactions', () => { it(`${fnName} should be successful if signed by taker and called by sender`, async () => { const orders = [await orderFactory.newSignedOrderAsync()]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const transactionReceipt = await exchangeWrapper.executeTransactionAsync( transaction, senderAddress, @@ -211,7 +251,7 @@ describe('Exchange transactions', () => { it(`${fnName} should be successful if called by taker without a transaction signature`, async () => { const orders = [await orderFactory.newSignedOrderAsync()]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); transaction.signature = constants.NULL_BYTES; const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, takerAddress); const fillLogs = transactionReceipt.logs.filter( @@ -235,7 +275,7 @@ describe('Exchange transactions', () => { const order = await orderFactory.newSignedOrderAsync(); const orders = [order]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const returnData = await exchangeInstance.executeTransaction.callAsync( transaction, transaction.signature, @@ -259,7 +299,7 @@ describe('Exchange transactions', () => { it(`${fnName} should revert if transaction has already been executed`, async () => { const orders = [await orderFactory.newSignedOrderAsync()]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); await exchangeWrapper.executeTransactionAsync(transaction, senderAddress); const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); const expectedError = new ExchangeRevertErrors.TransactionError( @@ -272,13 +312,15 @@ describe('Exchange transactions', () => { it(`${fnName} should revert and rethrow error if executeTransaction is called recursively with a signature`, async () => { const orders = [await orderFactory.newSignedOrderAsync()]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); const recursiveData = exchangeInstance.executeTransaction.getABIEncodedTransactionData( transaction, transaction.signature, ); - const recursiveTransaction = takerTransactionFactory.newSignedTransaction(recursiveData); + const recursiveTransaction = await takerTransactionFactory.newSignedTransactionAsync({ + data: recursiveData, + }); const recursiveTransactionHashHex = transactionHashUtils.getTransactionHashHex( recursiveTransaction, ); @@ -296,12 +338,14 @@ describe('Exchange transactions', () => { it(`${fnName} should be successful if executeTransaction is called recursively by taker without a signature`, async () => { const orders = [await orderFactory.newSignedOrderAsync()]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const recursiveData = exchangeInstance.executeTransaction.getABIEncodedTransactionData( transaction, constants.NULL_BYTES, ); - const recursiveTransaction = takerTransactionFactory.newSignedTransaction(recursiveData); + const recursiveTransaction = await takerTransactionFactory.newSignedTransactionAsync({ + data: recursiveData, + }); const transactionReceipt = await exchangeWrapper.executeTransactionAsync( recursiveTransaction, takerAddress, @@ -336,7 +380,7 @@ describe('Exchange transactions', () => { order.signature = constants.NULL_BYTES; const orders = [order]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); const nestedError = new ExchangeRevertErrors.SignatureError( ExchangeRevertErrors.SignatureErrorCode.InvalidLength, @@ -359,7 +403,7 @@ describe('Exchange transactions', () => { const order = await orderFactory.newSignedOrderAsync(); const orders = [order]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, orders); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); const nestedError = new ExchangeRevertErrors.InvalidMakerError( orderHashUtils.getOrderHashHex(order), @@ -375,7 +419,7 @@ describe('Exchange transactions', () => { it('should be successful if signed by maker and called by sender', async () => { const orders = [await orderFactory.newSignedOrderAsync()]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, orders); - const transaction = makerTransactionFactory.newSignedTransaction(data); + const transaction = await makerTransactionFactory.newSignedTransactionAsync({ data }); const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, senderAddress); const cancelLogs = transactionReceipt.logs.filter( log => (log as LogWithDecodedArgs).event === 'Cancel', @@ -392,7 +436,7 @@ describe('Exchange transactions', () => { it('should be successful if called by maker without a signature', async () => { const orders = [await orderFactory.newSignedOrderAsync()]; const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, orders); - const transaction = makerTransactionFactory.newSignedTransaction(data); + const transaction = await makerTransactionFactory.newSignedTransactionAsync({ data }); transaction.signature = constants.NULL_BYTES; const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, makerAddress); const cancelLogs = transactionReceipt.logs.filter( @@ -415,7 +459,7 @@ describe('Exchange transactions', () => { ExchangeFunctionName.BatchCancelOrders, orders, ); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); const nestedError = new ExchangeRevertErrors.InvalidMakerError( orderHashUtils.getOrderHashHex(orders[0]), @@ -434,7 +478,7 @@ describe('Exchange transactions', () => { ExchangeFunctionName.BatchCancelOrders, orders, ); - const transaction = makerTransactionFactory.newSignedTransaction(data); + const transaction = await makerTransactionFactory.newSignedTransactionAsync({ data }); const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, senderAddress); const cancelLogs = transactionReceipt.logs.filter( log => (log as LogWithDecodedArgs).event === 'Cancel', @@ -456,7 +500,7 @@ describe('Exchange transactions', () => { ExchangeFunctionName.BatchCancelOrders, orders, ); - const transaction = makerTransactionFactory.newSignedTransaction(data); + const transaction = await makerTransactionFactory.newSignedTransactionAsync({ data }); transaction.signature = constants.NULL_BYTES; const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, makerAddress); const cancelLogs = transactionReceipt.logs.filter( @@ -478,7 +522,7 @@ describe('Exchange transactions', () => { it('should be successful if signed by maker and called by sender', async () => { const targetEpoch = constants.ZERO_AMOUNT; const data = exchangeInstance.cancelOrdersUpTo.getABIEncodedTransactionData(targetEpoch); - const transaction = makerTransactionFactory.newSignedTransaction(data); + const transaction = await makerTransactionFactory.newSignedTransactionAsync({ data }); const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, senderAddress); const cancelLogs = transactionReceipt.logs.filter( log => (log as LogWithDecodedArgs).event === 'CancelUpTo', @@ -492,7 +536,7 @@ describe('Exchange transactions', () => { it('should be successful if called by maker without a signature', async () => { const targetEpoch = constants.ZERO_AMOUNT; const data = exchangeInstance.cancelOrdersUpTo.getABIEncodedTransactionData(targetEpoch); - const transaction = makerTransactionFactory.newSignedTransaction(data); + const transaction = await makerTransactionFactory.newSignedTransactionAsync({ data }); const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, makerAddress); const cancelLogs = transactionReceipt.logs.filter( log => (log as LogWithDecodedArgs).event === 'CancelUpTo', @@ -509,7 +553,7 @@ describe('Exchange transactions', () => { const order = await orderFactory.newSignedOrderAsync(); const orderHash = orderHashUtils.getOrderHashHex(order); const data = exchangeInstance.preSign.getABIEncodedTransactionData(orderHash); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); let isPreSigned = await exchangeInstance.preSigned.callAsync(orderHash, takerAddress); expect(isPreSigned).to.be.eq(false); await exchangeWrapper.executeTransactionAsync(transaction, senderAddress); @@ -520,7 +564,7 @@ describe('Exchange transactions', () => { const order = await orderFactory.newSignedOrderAsync(); const orderHash = orderHashUtils.getOrderHashHex(order); const data = exchangeInstance.preSign.getABIEncodedTransactionData(orderHash); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); transaction.signature = constants.NULL_BYTES; let isPreSigned = await exchangeInstance.preSigned.callAsync(orderHash, takerAddress); expect(isPreSigned).to.be.eq(false); @@ -536,7 +580,7 @@ describe('Exchange transactions', () => { validatorAddress, shouldApprove, ); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, senderAddress); const validatorApprovalLogs = transactionReceipt.logs.filter( log => @@ -557,7 +601,7 @@ describe('Exchange transactions', () => { validatorAddress, shouldApprove, ); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); transaction.signature = constants.NULL_BYTES; const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, takerAddress); const validatorApprovalLogs = transactionReceipt.logs.filter( @@ -581,7 +625,7 @@ describe('Exchange transactions', () => { validatorAddress, shouldApprove, ); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, senderAddress); const validatorApprovalLogs = transactionReceipt.logs.filter( log => @@ -602,7 +646,7 @@ describe('Exchange transactions', () => { validatorAddress, shouldApprove, ); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); transaction.signature = constants.NULL_BYTES; const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, takerAddress); const validatorApprovalLogs = transactionReceipt.logs.filter( @@ -625,12 +669,34 @@ describe('Exchange transactions', () => { const order2 = await orderFactory.newSignedOrderAsync(); const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]); const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]); - const transaction1 = takerTransactionFactory.newSignedTransaction(data1); - const transaction2 = taker2TransactionFactory.newSignedTransaction(data2); + const transaction1 = await takerTransactionFactory.newSignedTransactionAsync({ data: data1 }); + const transaction2 = await taker2TransactionFactory.newSignedTransactionAsync({ data: data2 }); const transactionReceipt = await exchangeWrapper.batchExecuteTransactionsAsync( [transaction1, transaction2], senderAddress, ); + + const transactionExecutionLogs = transactionReceipt.logs.filter( + log => + (log as LogWithDecodedArgs).event === + 'TransactionExecution', + ); + expect(transactionExecutionLogs.length).to.eq(2); + + const execution1LogArgs = (transactionExecutionLogs[0] as LogWithDecodedArgs< + ExchangeTransactionExecutionEventArgs + >).args; + expect(execution1LogArgs.transactionHash).to.equal( + transactionHashUtils.getTransactionHashHex(transaction1), + ); + + const execution2LogArgs = (transactionExecutionLogs[1] as LogWithDecodedArgs< + ExchangeTransactionExecutionEventArgs + >).args; + expect(execution2LogArgs.transactionHash).to.equal( + transactionHashUtils.getTransactionHashHex(transaction2), + ); + const fillLogs = transactionReceipt.logs.filter( log => (log as LogWithDecodedArgs).event === 'Fill', ); @@ -667,14 +733,36 @@ describe('Exchange transactions', () => { const order2 = await orderFactory.newSignedOrderAsync(); const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]); const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]); - const transaction1 = takerTransactionFactory.newSignedTransaction(data1); - const transaction2 = takerTransactionFactory.newSignedTransaction(data2); + const transaction1 = await takerTransactionFactory.newSignedTransactionAsync({ data: data1 }); + const transaction2 = await takerTransactionFactory.newSignedTransactionAsync({ data: data2 }); transaction1.signature = constants.NULL_BYTES; transaction2.signature = constants.NULL_BYTES; const transactionReceipt = await exchangeWrapper.batchExecuteTransactionsAsync( [transaction1, transaction2], takerAddress, ); + + const transactionExecutionLogs = transactionReceipt.logs.filter( + log => + (log as LogWithDecodedArgs).event === + 'TransactionExecution', + ); + expect(transactionExecutionLogs.length).to.eq(2); + + const execution1LogArgs = (transactionExecutionLogs[0] as LogWithDecodedArgs< + ExchangeTransactionExecutionEventArgs + >).args; + expect(execution1LogArgs.transactionHash).to.equal( + transactionHashUtils.getTransactionHashHex(transaction1), + ); + + const execution2LogArgs = (transactionExecutionLogs[1] as LogWithDecodedArgs< + ExchangeTransactionExecutionEventArgs + >).args; + expect(execution2LogArgs.transactionHash).to.equal( + transactionHashUtils.getTransactionHashHex(transaction2), + ); + const fillLogs = transactionReceipt.logs.filter( log => (log as LogWithDecodedArgs).event === 'Fill', ); @@ -711,13 +799,35 @@ describe('Exchange transactions', () => { const order2 = await orderFactory.newSignedOrderAsync(); const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]); const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]); - const transaction1 = takerTransactionFactory.newSignedTransaction(data1); - const transaction2 = taker2TransactionFactory.newSignedTransaction(data2); + const transaction1 = await takerTransactionFactory.newSignedTransactionAsync({ data: data1 }); + const transaction2 = await taker2TransactionFactory.newSignedTransactionAsync({ data: data2 }); transaction2.signature = constants.NULL_BYTES; const transactionReceipt = await exchangeWrapper.batchExecuteTransactionsAsync( [transaction1, transaction2], taker2Address, ); + + const transactionExecutionLogs = transactionReceipt.logs.filter( + log => + (log as LogWithDecodedArgs).event === + 'TransactionExecution', + ); + expect(transactionExecutionLogs.length).to.eq(2); + + const execution1LogArgs = (transactionExecutionLogs[0] as LogWithDecodedArgs< + ExchangeTransactionExecutionEventArgs + >).args; + expect(execution1LogArgs.transactionHash).to.equal( + transactionHashUtils.getTransactionHashHex(transaction1), + ); + + const execution2LogArgs = (transactionExecutionLogs[1] as LogWithDecodedArgs< + ExchangeTransactionExecutionEventArgs + >).args; + expect(execution2LogArgs.transactionHash).to.equal( + transactionHashUtils.getTransactionHashHex(transaction2), + ); + const fillLogs = transactionReceipt.logs.filter( log => (log as LogWithDecodedArgs).event === 'Fill', ); @@ -754,8 +864,8 @@ describe('Exchange transactions', () => { const order2 = await orderFactory.newSignedOrderAsync(); const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]); const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]); - const transaction1 = takerTransactionFactory.newSignedTransaction(data1); - const transaction2 = taker2TransactionFactory.newSignedTransaction(data2); + const transaction1 = await takerTransactionFactory.newSignedTransactionAsync({ data: data1 }); + const transaction2 = await taker2TransactionFactory.newSignedTransactionAsync({ data: data2 }); const returnData = await exchangeInstance.batchExecuteTransactions.callAsync( [transaction1, transaction2], [transaction1.signature, transaction2.signature], @@ -784,13 +894,34 @@ describe('Exchange transactions', () => { const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [ order2, ]); - const transaction1 = takerTransactionFactory.newSignedTransaction(data1); - const transaction2 = makerTransactionFactory.newSignedTransaction(data2); + const transaction1 = await takerTransactionFactory.newSignedTransactionAsync({ data: data1 }); + const transaction2 = await makerTransactionFactory.newSignedTransactionAsync({ data: data2 }); const transactionReceipt = await exchangeWrapper.batchExecuteTransactionsAsync( [transaction1, transaction2], senderAddress, ); + const transactionExecutionLogs = transactionReceipt.logs.filter( + log => + (log as LogWithDecodedArgs).event === + 'TransactionExecution', + ); + expect(transactionExecutionLogs.length).to.eq(2); + + const execution1LogArgs = (transactionExecutionLogs[0] as LogWithDecodedArgs< + ExchangeTransactionExecutionEventArgs + >).args; + expect(execution1LogArgs.transactionHash).to.equal( + transactionHashUtils.getTransactionHashHex(transaction1), + ); + + const execution2LogArgs = (transactionExecutionLogs[1] as LogWithDecodedArgs< + ExchangeTransactionExecutionEventArgs + >).args; + expect(execution2LogArgs.transactionHash).to.equal( + transactionHashUtils.getTransactionHashHex(transaction2), + ); + let fillLogIndex: number = 0; let cancelLogIndex: number = 0; const fillLogs = transactionReceipt.logs.filter((log, index) => { @@ -839,8 +970,8 @@ describe('Exchange transactions', () => { const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [ order2, ]); - const transaction1 = takerTransactionFactory.newSignedTransaction(data1); - const transaction2 = makerTransactionFactory.newSignedTransaction(data2); + const transaction1 = await takerTransactionFactory.newSignedTransactionAsync({ data: data1 }); + const transaction2 = await makerTransactionFactory.newSignedTransactionAsync({ data: data2 }); const returnData = await exchangeInstance.batchExecuteTransactions.callAsync( [transaction1, transaction2], [transaction1.signature, transaction2.signature], @@ -862,8 +993,8 @@ describe('Exchange transactions', () => { const order = await orderFactory.newSignedOrderAsync(); const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [order]); const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]); - const transaction1 = makerTransactionFactory.newSignedTransaction(data1); - const transaction2 = takerTransactionFactory.newSignedTransaction(data2); + const transaction1 = await makerTransactionFactory.newSignedTransactionAsync({ data: data1 }); + const transaction2 = await takerTransactionFactory.newSignedTransactionAsync({ data: data2 }); const tx = exchangeWrapper.batchExecuteTransactionsAsync([transaction1, transaction2], senderAddress); const nestedError = new ExchangeRevertErrors.OrderStatusError( orderHashUtils.getOrderHashHex(order), @@ -875,6 +1006,25 @@ describe('Exchange transactions', () => { ); return expect(tx).to.revertWith(expectedError); }); + it('should revert if a single transaction is expired', async () => { + const order1 = await orderFactory.newSignedOrderAsync(); + const order2 = await orderFactory.newSignedOrderAsync(); + const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]); + const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]); + const currentTimestamp = await getLatestBlockTimestampAsync(); + const transaction1 = await takerTransactionFactory.newSignedTransactionAsync({ data: data1 }); + const transaction2 = await taker2TransactionFactory.newSignedTransactionAsync({ + data: data2, + expirationTimeSeconds: new BigNumber(currentTimestamp).minus(10), + }); + const tx = exchangeWrapper.batchExecuteTransactionsAsync([transaction1, transaction2], senderAddress); + const expiredTransactionHash = transactionHashUtils.getTransactionHashHex(transaction2); + const expectedError = new ExchangeRevertErrors.TransactionError( + ExchangeRevertErrors.TransactionErrorCode.Expired, + expiredTransactionHash, + ); + return expect(tx).to.revertWith(expectedError); + }); }); describe('examples', () => { describe('ExchangeWrapper', () => { @@ -901,10 +1051,13 @@ describe('Exchange transactions', () => { }); const targetOrderEpoch = orderSalt.plus(1); const cancelData = exchangeInstance.cancelOrdersUpTo.getABIEncodedTransactionData(targetOrderEpoch); - const cancelTransaction = makerTransactionFactory.newSignedTransaction(cancelData); + const cancelTransaction = await makerTransactionFactory.newSignedTransactionAsync({ + data: cancelData, + }); await exchangeWrapperContract.cancelOrdersUpTo.awaitTransactionSuccessAsync( targetOrderEpoch, cancelTransaction.salt, + cancelTransaction.expirationTimeSeconds, cancelTransaction.signature, { from: makerAddress }, constants.AWAIT_TRANSACTION_MINED_MS, @@ -916,7 +1069,7 @@ describe('Exchange transactions', () => { takerAssetFillAmount, signedOrder.signature, ); - const fillTransaction = takerTransactionFactory.newSignedTransaction(fillData); + const fillTransaction = await takerTransactionFactory.newSignedTransactionAsync({ data: fillData }); const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const transactionHashHex = transactionHashUtils.getTransactionHashHex(fillTransaction); const expectedError = new ExchangeRevertErrors.TransactionExecutionError( @@ -927,6 +1080,7 @@ describe('Exchange transactions', () => { signedOrder, takerAssetFillAmount, fillTransaction.salt, + fillTransaction.expirationTimeSeconds, signedOrder.signature, fillTransaction.signature, { from: takerAddress }, @@ -949,13 +1103,14 @@ describe('Exchange transactions', () => { takerAssetFillAmount, signedOrder.signature, ); - const transaction = takerTransactionFactory.newSignedTransaction(data); + const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const logDecoder = new LogDecoder(web3Wrapper, artifacts); const transactionReceipt = await logDecoder.getTxWithDecodedLogsAsync( await exchangeWrapperContract.fillOrder.sendTransactionAsync( signedOrder, takerAssetFillAmount, transaction.salt, + transaction.expirationTimeSeconds, signedOrder.signature, transaction.signature, { from: takerAddress }, diff --git a/contracts/test-utils/src/transaction_factory.ts b/contracts/test-utils/src/transaction_factory.ts index 7759e90dc1..9f0b4e165b 100644 --- a/contracts/test-utils/src/transaction_factory.ts +++ b/contracts/test-utils/src/transaction_factory.ts @@ -1,7 +1,9 @@ import { generatePseudoRandomSalt, transactionHashUtils } from '@0x/order-utils'; -import { SignatureType, SignedZeroExTransaction } from '@0x/types'; +import { SignatureType, SignedZeroExTransaction, ZeroExTransaction } from '@0x/types'; +import { BigNumber } from '@0x/utils'; import * as ethUtil from 'ethereumjs-util'; +import { getLatestBlockTimestampAsync } from './block_timestamp'; import { signingUtils } from './signing_utils'; export class TransactionFactory { @@ -16,20 +18,27 @@ export class TransactionFactory { this._chainId = chainId; this._signerBuff = ethUtil.privateToAddress(this._privateKey); } - public newSignedTransaction( - data: string, + public async newSignedTransactionAsync( + customTransactionParams: Partial, signatureType: SignatureType = SignatureType.EthSign, - ): SignedZeroExTransaction { + ): Promise { + if (customTransactionParams.data === undefined) { + throw new Error('Error: ZeroExTransaction data field must be supplied'); + } + const tenMinutesInSeconds = 10 * 60; + const currentBlockTimestamp = await getLatestBlockTimestampAsync(); const salt = generatePseudoRandomSalt(); const signerAddress = `0x${this._signerBuff.toString('hex')}`; const transaction = { salt, signerAddress, - data, + data: customTransactionParams.data, + expirationTimeSeconds: new BigNumber(currentBlockTimestamp).plus(tenMinutesInSeconds), domain: { verifyingContractAddress: this._exchangeAddress, chainId: this._chainId, }, + ...customTransactionParams, }; const transactionHashBuffer = transactionHashUtils.getTransactionHashBuffer(transaction); diff --git a/packages/json-schemas/CHANGELOG.json b/packages/json-schemas/CHANGELOG.json index 39aed7f09a..5230ef882a 100644 --- a/packages/json-schemas/CHANGELOG.json +++ b/packages/json-schemas/CHANGELOG.json @@ -13,6 +13,10 @@ { "note": "Add `makerAssetData` and `takerAssetData` to `Order` schemas", "pr": 1819 + }, + { + "note": "Add `expirationTimeSeconds` to `ZeroExTransaction` schema", + "pr": 1832 } ] }, diff --git a/packages/json-schemas/schemas/zero_ex_transaction_schema.json b/packages/json-schemas/schemas/zero_ex_transaction_schema.json index b0578b9968..240e02ab7e 100644 --- a/packages/json-schemas/schemas/zero_ex_transaction_schema.json +++ b/packages/json-schemas/schemas/zero_ex_transaction_schema.json @@ -4,8 +4,9 @@ "data": { "$ref": "/hexSchema" }, "signerAddress": { "$ref": "/addressSchema" }, "salt": { "$ref": "/wholeNumberSchema" }, + "expirationTimeSeconds": { "$ref": "/wholeNumberSchema" }, "domain": { "$ref": "/eip712DomainSchema" } }, - "required": ["data", "salt", "signerAddress", "domain"], + "required": ["data", "salt", "expirationTimeSeconds", "signerAddress", "domain"], "type": "object" } diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index 05a2a6bc1e..d864bc03ae 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -33,6 +33,14 @@ { "note": "Update `RevertError` types for new base constructor", "pr": 1819 + }, + { + "note": "Add `Expired` TransactionErrorCode", + "pr": 1832 + }, + { + "note": "Add `expirationTimeSeconds` to `ZeroExTransaction` parameters used for hashing", + "pr": 1832 } ] }, diff --git a/packages/order-utils/src/constants.ts b/packages/order-utils/src/constants.ts index d56ae9aab4..c0c03229d0 100644 --- a/packages/order-utils/src/constants.ts +++ b/packages/order-utils/src/constants.ts @@ -102,6 +102,7 @@ export const constants = { name: 'ZeroExTransaction', parameters: [ { name: 'salt', type: 'uint256' }, + { name: 'expirationTimeSeconds', type: 'uint256' }, { name: 'signerAddress', type: 'address' }, { name: 'data', type: 'bytes' }, ], diff --git a/packages/order-utils/src/exchange_revert_errors.ts b/packages/order-utils/src/exchange_revert_errors.ts index e6dbc24667..ee7e16aaa9 100644 --- a/packages/order-utils/src/exchange_revert_errors.ts +++ b/packages/order-utils/src/exchange_revert_errors.ts @@ -27,6 +27,7 @@ export enum AssetProxyDispatchErrorCode { export enum TransactionErrorCode { NoReentrancy, AlreadyExecuted, + Expired, } export class SignatureError extends RevertError { diff --git a/packages/order-utils/test/eip712_utils_test.ts b/packages/order-utils/test/eip712_utils_test.ts index f92a42fc1d..ec306c8973 100644 --- a/packages/order-utils/test/eip712_utils_test.ts +++ b/packages/order-utils/test/eip712_utils_test.ts @@ -57,7 +57,8 @@ describe('EIP712 Utils', () => { describe('createZeroExTransactionTypedData', () => { it('adds in the EIP712DomainSeparator', () => { const typedData = eip712Utils.createZeroExTransactionTypedData({ - salt: new BigNumber('0'), + salt: new BigNumber(0), + expirationTimeSeconds: new BigNumber(0), data: constants.NULL_BYTES, signerAddress: constants.NULL_ADDRESS, domain: { diff --git a/packages/order-utils/test/transaction_hash_test.ts b/packages/order-utils/test/transaction_hash_test.ts index 77d838081c..d38961c01f 100644 --- a/packages/order-utils/test/transaction_hash_test.ts +++ b/packages/order-utils/test/transaction_hash_test.ts @@ -14,12 +14,13 @@ const expect = chai.expect; describe('0x transaction hashing', () => { describe('#getTransactionHashHex', () => { - const expectedTransactionHash = '0x834125acbd69d6e2e706df216865728e9b63bfd7c8bcecb5987d9d02ea62ecd5'; + const expectedTransactionHash = '0x9779e4ca195f8c9c6f137f495599e9a1944806310b64748479bfa6c6b1ae7eb4'; const fakeVerifyingContractAddress = '0x5e72914535f202659083db3a02c984188fa26e9f'; const fakeChainId = 1337; const transaction: ZeroExTransaction = { signerAddress: constants.NULL_ADDRESS, salt: new BigNumber(0), + expirationTimeSeconds: new BigNumber(0), data: constants.NULL_BYTES, domain: { verifyingContractAddress: fakeVerifyingContractAddress, @@ -38,6 +39,7 @@ describe('0x transaction hashing', () => { const transactionHash = transactionHashUtils.getTransactionHashHex({ ...transaction, salt: '0', + expirationTimeSeconds: '0', } as any); expect(transactionHash).to.be.equal(expectedTransactionHash); }); diff --git a/packages/types/CHANGELOG.json b/packages/types/CHANGELOG.json index 1bb028e53c..904f83521a 100644 --- a/packages/types/CHANGELOG.json +++ b/packages/types/CHANGELOG.json @@ -25,6 +25,10 @@ { "note": "Update `Order` type for arbitrary fee tokens (ZEIP-28).", "pr": 1819 + }, + { + "note": "Add `expirationTimeSeconds` to `ZeroExTransaction` type", + "pr": 1832 } ] }, diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 606ff6b977..5964f2d46b 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -50,6 +50,7 @@ export interface SignedOrder extends Order { */ export interface ZeroExTransaction { salt: BigNumber; + expirationTimeSeconds: BigNumber; signerAddress: string; data: string; domain: EIP712DomainWithDefaultSchema;