diff --git a/contracts/erc20/CHANGELOG.json b/contracts/erc20/CHANGELOG.json index 25d97dbec2..f99ac25de1 100644 --- a/contracts/erc20/CHANGELOG.json +++ b/contracts/erc20/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "3.3.0", + "changes": [ + { + "note": "Allow for excess return data in `LibERC20TokenV06` compat* functions", + "pr": 97 + } + ] + }, { "timestamp": 1608692071, "version": "3.2.14", diff --git a/contracts/erc20/contracts/src/v06/LibERC20TokenV06.sol b/contracts/erc20/contracts/src/v06/LibERC20TokenV06.sol index a7a2b4c0a9..1974bc7e3d 100644 --- a/contracts/erc20/contracts/src/v06/LibERC20TokenV06.sol +++ b/contracts/erc20/contracts/src/v06/LibERC20TokenV06.sol @@ -118,7 +118,7 @@ library LibERC20TokenV06 { { tokenDecimals = 18; (bool didSucceed, bytes memory resultData) = address(token).staticcall(DECIMALS_CALL_DATA); - if (didSucceed && resultData.length == 32) { + if (didSucceed && resultData.length >= 32) { tokenDecimals = uint8(LibBytesV06.readUint256(resultData, 0)); } } @@ -141,7 +141,7 @@ library LibERC20TokenV06 { spender ) ); - if (didSucceed && resultData.length == 32) { + if (didSucceed && resultData.length >= 32) { allowance_ = LibBytesV06.readUint256(resultData, 0); } } @@ -162,7 +162,7 @@ library LibERC20TokenV06 { owner ) ); - if (didSucceed && resultData.length == 32) { + if (didSucceed && resultData.length >= 32) { balance = LibBytesV06.readUint256(resultData, 0); } } @@ -180,7 +180,7 @@ library LibERC20TokenV06 { if (resultData.length == 0) { return true; } - if (resultData.length == 32) { + if (resultData.length >= 32) { uint256 result = LibBytesV06.readUint256(resultData, 0); if (result == 1) { return true; diff --git a/contracts/utils/CHANGELOG.json b/contracts/utils/CHANGELOG.json index 0078c3c124..61e93d48f6 100644 --- a/contracts/utils/CHANGELOG.json +++ b/contracts/utils/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "4.7.0", + "changes": [ + { + "note": "Add `LibSafeMathV06.safeDowncastToUint128()`", + "pr": 97 + } + ] + }, { "timestamp": 1608692071, "version": "4.6.5", diff --git a/contracts/utils/contracts/src/v06/LibSafeMathV06.sol b/contracts/utils/contracts/src/v06/LibSafeMathV06.sol index 3a3940d9d8..88dea00b64 100644 --- a/contracts/utils/contracts/src/v06/LibSafeMathV06.sol +++ b/contracts/utils/contracts/src/v06/LibSafeMathV06.sol @@ -187,4 +187,18 @@ library LibSafeMathV06 { { return a < b ? a : b; } + + function safeDowncastToUint128(uint256 a) + internal + pure + returns (uint128) + { + if (a > type(uint128).max) { + LibRichErrorsV06.rrevert(LibSafeMathRichErrorsV06.Uint256DowncastError( + LibSafeMathRichErrorsV06.DowncastErrorCodes.VALUE_TOO_LARGE_TO_DOWNCAST_TO_UINT128, + a + )); + } + return uint128(a); + } } diff --git a/contracts/utils/contracts/src/v06/errors/LibSafeMathRichErrorsV06.sol b/contracts/utils/contracts/src/v06/errors/LibSafeMathRichErrorsV06.sol index ef6beac09a..cc5468f45d 100644 --- a/contracts/utils/contracts/src/v06/errors/LibSafeMathRichErrorsV06.sol +++ b/contracts/utils/contracts/src/v06/errors/LibSafeMathRichErrorsV06.sol @@ -39,7 +39,8 @@ library LibSafeMathRichErrorsV06 { enum DowncastErrorCodes { VALUE_TOO_LARGE_TO_DOWNCAST_TO_UINT32, VALUE_TOO_LARGE_TO_DOWNCAST_TO_UINT64, - VALUE_TOO_LARGE_TO_DOWNCAST_TO_UINT96 + VALUE_TOO_LARGE_TO_DOWNCAST_TO_UINT96, + VALUE_TOO_LARGE_TO_DOWNCAST_TO_UINT128 } // solhint-disable func-name-mixedcase diff --git a/contracts/zero-ex/CHANGELOG.json b/contracts/zero-ex/CHANGELOG.json index a06c5df1ea..da541244af 100644 --- a/contracts/zero-ex/CHANGELOG.json +++ b/contracts/zero-ex/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "0.17.0", + "changes": [ + { + "note": "Add DevUtils-like functions to `NativeOrdersFeature`", + "pr": 97 + } + ] + }, { "version": "0.16.0", "changes": [ diff --git a/contracts/zero-ex/contracts/src/features/INativeOrdersFeature.sol b/contracts/zero-ex/contracts/src/features/INativeOrdersFeature.sol index 53943909fd..aafa2c4eb0 100644 --- a/contracts/zero-ex/contracts/src/features/INativeOrdersFeature.sol +++ b/contracts/zero-ex/contracts/src/features/INativeOrdersFeature.sol @@ -346,4 +346,81 @@ interface INativeOrdersFeature { view returns (uint32 multiplier); + /// @dev Get order info, fillable amount, and signature validity for a limit order. + /// Fillable amount is determined using balances and allowances of the maker. + /// @param order The limit order. + /// @param signature The order signature. + /// @return orderInfo Info about the order. + /// @return actualFillableTakerTokenAmount How much of the order is fillable + /// based on maker funds, in taker tokens. + /// @return isSignatureValid Whether the signature is valid. + function getLimitOrderRelevantState( + LibNativeOrder.LimitOrder calldata order, + LibSignature.Signature calldata signature + ) + external + view + returns ( + LibNativeOrder.OrderInfo memory orderInfo, + uint128 actualFillableTakerTokenAmount, + bool isSignatureValid + ); + + /// @dev Get order info, fillable amount, and signature validity for an RFQ order. + /// Fillable amount is determined using balances and allowances of the maker. + /// @param order The RFQ order. + /// @param signature The order signature. + /// @return orderInfo Info about the order. + /// @return actualFillableTakerTokenAmount How much of the order is fillable + /// based on maker funds, in taker tokens. + /// @return isSignatureValid Whether the signature is valid. + function getRfqOrderRelevantState( + LibNativeOrder.RfqOrder calldata order, + LibSignature.Signature calldata signature + ) + external + view + returns ( + LibNativeOrder.OrderInfo memory orderInfo, + uint128 actualFillableTakerTokenAmount, + bool isSignatureValid + ); + + /// @dev Batch version of `getLimitOrderRelevantState()`. + /// @param orders The limit orders. + /// @param signatures The order signatures. + /// @return orderInfos Info about the orders. + /// @return actualFillableTakerTokenAmounts How much of each order is fillable + /// based on maker funds, in taker tokens. + /// @return isSignatureValids Whether each signature is valid for the order. + function batchGetLimitOrderRelevantStates( + LibNativeOrder.LimitOrder[] calldata orders, + LibSignature.Signature[] calldata signatures + ) + external + view + returns ( + LibNativeOrder.OrderInfo[] memory orderInfos, + uint128[] memory actualFillableTakerTokenAmounts, + bool[] memory isSignatureValids + ); + + /// @dev Batch version of `getRfqOrderRelevantState()`. + /// @param orders The RFQ orders. + /// @param signatures The order signatures. + /// @return orderInfos Info about the orders. + /// @return actualFillableTakerTokenAmounts How much of each order is fillable + /// based on maker funds, in taker tokens. + /// @return isSignatureValids Whether each signature is valid for the order. + function batchGetRfqOrderRelevantStates( + LibNativeOrder.RfqOrder[] calldata orders, + LibSignature.Signature[] calldata signatures + ) + external + view + returns ( + LibNativeOrder.OrderInfo[] memory orderInfos, + uint128[] memory actualFillableTakerTokenAmounts, + bool[] memory isSignatureValids + ); } diff --git a/contracts/zero-ex/contracts/src/features/NativeOrdersFeature.sol b/contracts/zero-ex/contracts/src/features/NativeOrdersFeature.sol index 5687f6e97b..d8f7d64cc7 100644 --- a/contracts/zero-ex/contracts/src/features/NativeOrdersFeature.sol +++ b/contracts/zero-ex/contracts/src/features/NativeOrdersFeature.sol @@ -51,6 +51,7 @@ contract NativeOrdersFeature is using LibSafeMathV06 for uint256; using LibSafeMathV06 for uint128; using LibRichErrorsV06 for bytes; + using LibERC20TokenV06 for IERC20TokenV06; /// @dev Params for `_settleOrder()`. struct SettleOrderInfo { @@ -97,6 +98,15 @@ contract NativeOrdersFeature is uint128 takerTokenFeeFilledAmount; } + // @dev Params for `_getActualFillableTakerTokenAmount()`. + struct GetActualFillableTakerTokenAmountParams { + address maker; + IERC20TokenV06 makerToken; + uint128 orderMakerAmount; + uint128 orderTakerAmount; + LibNativeOrder.OrderInfo orderInfo; + } + /// @dev Name of this feature. string public constant override FEATURE_NAME = "LimitOrders"; /// @dev Version of this feature. @@ -148,6 +158,10 @@ contract NativeOrdersFeature is _registerFeatureFunction(this.getRfqOrderHash.selector); _registerFeatureFunction(this.getProtocolFeeMultiplier.selector); _registerFeatureFunction(this.registerAllowedRfqOrigins.selector); + _registerFeatureFunction(this.getLimitOrderRelevantState.selector); + _registerFeatureFunction(this.getRfqOrderRelevantState.selector); + _registerFeatureFunction(this.batchGetLimitOrderRelevantStates.selector); + _registerFeatureFunction(this.batchGetRfqOrderRelevantStates.selector); return LibMigrate.MIGRATE_SUCCESS; } @@ -687,6 +701,148 @@ contract NativeOrdersFeature is ); } + /// @dev Get order info, fillable amount, and signature validity for a limit order. + /// Fillable amount is determined using balances and allowances of the maker. + /// @param order The limit order. + /// @param signature The order signature. + /// @return orderInfo Info about the order. + /// @return actualFillableTakerTokenAmount How much of the order is fillable + /// based on maker funds, in taker tokens. + /// @return isSignatureValid Whether the signature is valid. + function getLimitOrderRelevantState( + LibNativeOrder.LimitOrder memory order, + LibSignature.Signature calldata signature + ) + public + override + view + returns ( + LibNativeOrder.OrderInfo memory orderInfo, + uint128 actualFillableTakerTokenAmount, + bool isSignatureValid + ) + { + orderInfo = getLimitOrderInfo(order); + actualFillableTakerTokenAmount = _getActualFillableTakerTokenAmount( + GetActualFillableTakerTokenAmountParams({ + maker: order.maker, + makerToken: order.makerToken, + orderMakerAmount: order.makerAmount, + orderTakerAmount: order.takerAmount, + orderInfo: orderInfo + }) + ); + isSignatureValid = order.maker == + LibSignature.getSignerOfHash(orderInfo.orderHash, signature); + } + + /// @dev Get order info, fillable amount, and signature validity for an RFQ order. + /// Fillable amount is determined using balances and allowances of the maker. + /// @param order The RFQ order. + /// @param signature The order signature. + /// @return orderInfo Info about the order. + /// @return actualFillableTakerTokenAmount How much of the order is fillable + /// based on maker funds, in taker tokens. + /// @return isSignatureValid Whether the signature is valid. + function getRfqOrderRelevantState( + LibNativeOrder.RfqOrder memory order, + LibSignature.Signature memory signature + ) + public + override + view + returns ( + LibNativeOrder.OrderInfo memory orderInfo, + uint128 actualFillableTakerTokenAmount, + bool isSignatureValid + ) + { + orderInfo = getRfqOrderInfo(order); + actualFillableTakerTokenAmount = _getActualFillableTakerTokenAmount( + GetActualFillableTakerTokenAmountParams({ + maker: order.maker, + makerToken: order.makerToken, + orderMakerAmount: order.makerAmount, + orderTakerAmount: order.takerAmount, + orderInfo: orderInfo + }) + ); + isSignatureValid = order.maker == + LibSignature.getSignerOfHash(orderInfo.orderHash, signature); + } + + /// @dev Batch version of `getLimitOrderRelevantState()`. + /// @param orders The limit orders. + /// @param signatures The order signatures. + /// @return orderInfos Info about the orders. + /// @return actualFillableTakerTokenAmounts How much of each order is fillable + /// based on maker funds, in taker tokens. + /// @return isSignatureValids Whether each signature is valid for the order. + function batchGetLimitOrderRelevantStates( + LibNativeOrder.LimitOrder[] calldata orders, + LibSignature.Signature[] calldata signatures + ) + external + override + view + returns ( + LibNativeOrder.OrderInfo[] memory orderInfos, + uint128[] memory actualFillableTakerTokenAmounts, + bool[] memory isSignatureValids + ) + { + require( + orders.length == signatures.length, + "NativeOrdersFeature/MISMATCHED_ARRAY_LENGTHS" + ); + orderInfos = new LibNativeOrder.OrderInfo[](orders.length); + actualFillableTakerTokenAmounts = new uint128[](orders.length); + isSignatureValids = new bool[](orders.length); + for (uint256 i = 0; i < orders.length; ++i) { + ( + orderInfos[i], + actualFillableTakerTokenAmounts[i], + isSignatureValids[i] + ) = getLimitOrderRelevantState(orders[i], signatures[i]); + } + } + + /// @dev Batch version of `getRfqOrderRelevantState()`. + /// @param orders The RFQ orders. + /// @param signatures The order signatures. + /// @return orderInfos Info about the orders. + /// @return actualFillableTakerTokenAmounts How much of each order is fillable + /// based on maker funds, in taker tokens. + /// @return isSignatureValids Whether each signature is valid for the order. + function batchGetRfqOrderRelevantStates( + LibNativeOrder.RfqOrder[] calldata orders, + LibSignature.Signature[] calldata signatures + ) + external + override + view + returns ( + LibNativeOrder.OrderInfo[] memory orderInfos, + uint128[] memory actualFillableTakerTokenAmounts, + bool[] memory isSignatureValids + ) + { + require( + orders.length == signatures.length, + "NativeOrdersFeature/MISMATCHED_ARRAY_LENGTHS" + ); + orderInfos = new LibNativeOrder.OrderInfo[](orders.length); + actualFillableTakerTokenAmounts = new uint128[](orders.length); + isSignatureValids = new bool[](orders.length); + for (uint256 i = 0; i < orders.length; ++i) { + ( + orderInfos[i], + actualFillableTakerTokenAmounts[i], + isSignatureValids[i] + ) = getRfqOrderRelevantState(orders[i], signatures[i]); + } + } + /// @dev Get the protocol fee multiplier. This should be multiplied by the /// gas price to arrive at the required protocol fee to fill a native order. /// @return multiplier The protocol fee multiplier. @@ -751,6 +907,48 @@ contract NativeOrdersFeature is orderInfo.status = LibNativeOrder.OrderStatus.FILLABLE; } + /// @dev Calculate the actual fillable taker token amount of an order + /// based on maker allowance and balances. + function _getActualFillableTakerTokenAmount( + GetActualFillableTakerTokenAmountParams memory params + ) + private + view + returns (uint128 actualFillableTakerTokenAmount) + { + if (params.orderMakerAmount == 0 || params.orderTakerAmount == 0) { + // Empty order. + return 0; + } + if (params.orderInfo.status != LibNativeOrder.OrderStatus.FILLABLE) { + // Not fillable. + return 0; + } + + // Get the fillable maker amount based on the order quantities and + // previously filled amount + uint256 fillableMakerTokenAmount = LibMathV06.getPartialAmountFloor( + uint256( + params.orderTakerAmount + - params.orderInfo.takerTokenFilledAmount + ), + uint256(params.orderTakerAmount), + uint256(params.orderMakerAmount) + ); + // Clamp it to the amount of maker tokens we can spend on behalf of the + // maker. + fillableMakerTokenAmount = LibSafeMathV06.min256( + fillableMakerTokenAmount, + _getSpendableERC20BalanceOf(params.makerToken, params.maker) + ); + // Convert to taker token amount. + return LibMathV06.getPartialAmountCeil( + fillableMakerTokenAmount, + uint256(params.orderMakerAmount), + uint256(params.orderTakerAmount) + ).safeDowncastToUint128(); + } + /// @dev Cancel a limit or RFQ order directly by its order hash. /// @param orderHash The order's order hash. /// @param maker The order's maker. diff --git a/contracts/zero-ex/test/features/native_orders_feature_test.ts b/contracts/zero-ex/test/features/native_orders_feature_test.ts index b13bdba433..e5f198f3f7 100644 --- a/contracts/zero-ex/test/features/native_orders_feature_test.ts +++ b/contracts/zero-ex/test/features/native_orders_feature_test.ts @@ -1366,14 +1366,332 @@ blockchainTests.resets('NativeOrdersFeature', env => { }); }); - it.skip('RFQ gas benchmark', async () => { - const orders = [...new Array(2)].map(() => - getTestRfqOrder({ pool: '0x0000000000000000000000000000000000000000000000000000000000000000' }), - ); - // Fill one to warm up the fee pool. - await fillRfqOrderAsync(orders[0]); - const receipt = await fillRfqOrderAsync(orders[1]); - // tslint:disable-next-line: no-console - console.log(receipt.gasUsed); + async function fundOrderMakerAsync( + order: LimitOrder | RfqOrder, + balance: BigNumber = order.makerAmount, + allowance: BigNumber = order.makerAmount, + ): Promise { + await makerToken.burn(maker, await makerToken.balanceOf(maker).callAsync()).awaitTransactionSuccessAsync(); + await makerToken.mint(maker, balance).awaitTransactionSuccessAsync(); + await makerToken.approve(zeroEx.address, allowance).awaitTransactionSuccessAsync({ from: maker }); + } + + function getFillableMakerTokenAmount( + order: LimitOrder | RfqOrder, + takerTokenFilledAmount: BigNumber = ZERO_AMOUNT, + ): BigNumber { + return order.takerAmount + .minus(takerTokenFilledAmount) + .times(order.makerAmount) + .div(order.takerAmount) + .integerValue(BigNumber.ROUND_DOWN); + } + + function getActualFillableTakerTokenAmount( + order: LimitOrder | RfqOrder, + makerBalance: BigNumber = order.makerAmount, + makerAllowance: BigNumber = order.makerAmount, + takerTokenFilledAmount: BigNumber = ZERO_AMOUNT, + ): BigNumber { + const fillableMakerTokenAmount = getFillableMakerTokenAmount(order, takerTokenFilledAmount); + return BigNumber.min(fillableMakerTokenAmount, makerBalance, makerAllowance) + .times(order.takerAmount) + .div(order.makerAmount) + .integerValue(BigNumber.ROUND_UP); + } + + function getRandomFraction(precision: number = 2): string { + return Math.random().toPrecision(precision); + } + + describe('getLimitOrderRelevantState()', () => { + it('works with an empty order', async () => { + const order = getTestLimitOrder({ + takerAmount: ZERO_AMOUNT, + }); + await fundOrderMakerAsync(order); + const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx + .getLimitOrderRelevantState(order, await order.getSignatureWithProviderAsync(env.provider)) + .callAsync(); + expect(orderInfo).to.deep.eq({ + orderHash: order.getHash(), + status: OrderStatus.Filled, + takerTokenFilledAmount: ZERO_AMOUNT, + }); + expect(fillableTakerAmount).to.bignumber.eq(0); + expect(isSignatureValid).to.eq(true); + }); + + it('works with cancelled order', async () => { + const order = getTestLimitOrder(); + await fundOrderMakerAsync(order); + await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker }); + const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx + .getLimitOrderRelevantState(order, await order.getSignatureWithProviderAsync(env.provider)) + .callAsync(); + expect(orderInfo).to.deep.eq({ + orderHash: order.getHash(), + status: OrderStatus.Cancelled, + takerTokenFilledAmount: ZERO_AMOUNT, + }); + expect(fillableTakerAmount).to.bignumber.eq(0); + expect(isSignatureValid).to.eq(true); + }); + + it('works with a bad signature', async () => { + const order = getTestLimitOrder(); + await fundOrderMakerAsync(order); + const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx + .getLimitOrderRelevantState( + order, + await order.clone({ maker: notMaker }).getSignatureWithProviderAsync(env.provider), + ) + .callAsync(); + expect(orderInfo).to.deep.eq({ + orderHash: order.getHash(), + status: OrderStatus.Fillable, + takerTokenFilledAmount: ZERO_AMOUNT, + }); + expect(fillableTakerAmount).to.bignumber.eq(order.takerAmount); + expect(isSignatureValid).to.eq(false); + }); + + it('works with an unfilled order', async () => { + const order = getTestLimitOrder(); + await fundOrderMakerAsync(order); + const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx + .getLimitOrderRelevantState(order, await order.getSignatureWithProviderAsync(env.provider)) + .callAsync(); + expect(orderInfo).to.deep.eq({ + orderHash: order.getHash(), + status: OrderStatus.Fillable, + takerTokenFilledAmount: ZERO_AMOUNT, + }); + expect(fillableTakerAmount).to.bignumber.eq(order.takerAmount); + expect(isSignatureValid).to.eq(true); + }); + + it('works with a fully filled order', async () => { + const order = getTestLimitOrder(); + // Fully Fund maker and taker. + await fundOrderMakerAsync(order); + await takerToken + .mint(taker, order.takerAmount.plus(order.takerTokenFeeAmount)) + .awaitTransactionSuccessAsync(); + await fillLimitOrderAsync(order); + // Partially fill the order. + const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx + .getLimitOrderRelevantState(order, await order.getSignatureWithProviderAsync(env.provider)) + .callAsync(); + expect(orderInfo).to.deep.eq({ + orderHash: order.getHash(), + status: OrderStatus.Filled, + takerTokenFilledAmount: order.takerAmount, + }); + expect(fillableTakerAmount).to.bignumber.eq(0); + expect(isSignatureValid).to.eq(true); + }); + + it('works with an under-funded, partially-filled order', async () => { + const order = getTestLimitOrder(); + // Fully Fund maker and taker. + await fundOrderMakerAsync(order); + await takerToken + .mint(taker, order.takerAmount.plus(order.takerTokenFeeAmount)) + .awaitTransactionSuccessAsync(); + // Partially fill the order. + const fillAmount = order.takerAmount.times(getRandomFraction()).integerValue(); + await fillLimitOrderAsync(order, { fillAmount }); + // Reduce maker funds to be < remaining. + const remainingMakerAmount = getFillableMakerTokenAmount(order, fillAmount); + const balance = remainingMakerAmount.times(getRandomFraction()).integerValue(); + const allowance = remainingMakerAmount.times(getRandomFraction()).integerValue(); + await fundOrderMakerAsync(order, balance, allowance); + // Get order state. + const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx + .getLimitOrderRelevantState(order, await order.getSignatureWithProviderAsync(env.provider)) + .callAsync(); + expect(orderInfo).to.deep.eq({ + orderHash: order.getHash(), + status: OrderStatus.Fillable, + takerTokenFilledAmount: fillAmount, + }); + expect(fillableTakerAmount).to.bignumber.eq( + getActualFillableTakerTokenAmount(order, balance, allowance, fillAmount), + ); + expect(isSignatureValid).to.eq(true); + }); + }); + + describe('getRfqOrderRelevantState()', () => { + it('works with an empty order', async () => { + const order = getTestRfqOrder({ + takerAmount: ZERO_AMOUNT, + }); + await fundOrderMakerAsync(order); + const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx + .getRfqOrderRelevantState(order, await order.getSignatureWithProviderAsync(env.provider)) + .callAsync(); + expect(orderInfo).to.deep.eq({ + orderHash: order.getHash(), + status: OrderStatus.Filled, + takerTokenFilledAmount: ZERO_AMOUNT, + }); + expect(fillableTakerAmount).to.bignumber.eq(0); + expect(isSignatureValid).to.eq(true); + }); + + it('works with cancelled order', async () => { + const order = getTestRfqOrder(); + await fundOrderMakerAsync(order); + await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker }); + const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx + .getRfqOrderRelevantState(order, await order.getSignatureWithProviderAsync(env.provider)) + .callAsync(); + expect(orderInfo).to.deep.eq({ + orderHash: order.getHash(), + status: OrderStatus.Cancelled, + takerTokenFilledAmount: ZERO_AMOUNT, + }); + expect(fillableTakerAmount).to.bignumber.eq(0); + expect(isSignatureValid).to.eq(true); + }); + + it('works with a bad signature', async () => { + const order = getTestRfqOrder(); + await fundOrderMakerAsync(order); + const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx + .getRfqOrderRelevantState( + order, + await order.clone({ maker: notMaker }).getSignatureWithProviderAsync(env.provider), + ) + .callAsync(); + expect(orderInfo).to.deep.eq({ + orderHash: order.getHash(), + status: OrderStatus.Fillable, + takerTokenFilledAmount: ZERO_AMOUNT, + }); + expect(fillableTakerAmount).to.bignumber.eq(order.takerAmount); + expect(isSignatureValid).to.eq(false); + }); + + it('works with an unfilled order', async () => { + const order = getTestRfqOrder(); + await fundOrderMakerAsync(order); + const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx + .getRfqOrderRelevantState(order, await order.getSignatureWithProviderAsync(env.provider)) + .callAsync(); + expect(orderInfo).to.deep.eq({ + orderHash: order.getHash(), + status: OrderStatus.Fillable, + takerTokenFilledAmount: ZERO_AMOUNT, + }); + expect(fillableTakerAmount).to.bignumber.eq(order.takerAmount); + expect(isSignatureValid).to.eq(true); + }); + + it('works with a fully filled order', async () => { + const order = getTestRfqOrder(); + // Fully Fund maker and taker. + await fundOrderMakerAsync(order); + await takerToken.mint(taker, order.takerAmount); + await fillRfqOrderAsync(order); + // Partially fill the order. + const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx + .getRfqOrderRelevantState(order, await order.getSignatureWithProviderAsync(env.provider)) + .callAsync(); + expect(orderInfo).to.deep.eq({ + orderHash: order.getHash(), + status: OrderStatus.Filled, + takerTokenFilledAmount: order.takerAmount, + }); + expect(fillableTakerAmount).to.bignumber.eq(0); + expect(isSignatureValid).to.eq(true); + }); + + it('works with an under-funded, partially-filled order', async () => { + const order = getTestRfqOrder(); + // Fully Fund maker and taker. + await fundOrderMakerAsync(order); + await takerToken.mint(taker, order.takerAmount).awaitTransactionSuccessAsync(); + // Partially fill the order. + const fillAmount = order.takerAmount.times(getRandomFraction()).integerValue(); + await fillRfqOrderAsync(order, fillAmount); + // Reduce maker funds to be < remaining. + const remainingMakerAmount = getFillableMakerTokenAmount(order, fillAmount); + const balance = remainingMakerAmount.times(getRandomFraction()).integerValue(); + const allowance = remainingMakerAmount.times(getRandomFraction()).integerValue(); + await fundOrderMakerAsync(order, balance, allowance); + // Get order state. + const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx + .getRfqOrderRelevantState(order, await order.getSignatureWithProviderAsync(env.provider)) + .callAsync(); + expect(orderInfo).to.deep.eq({ + orderHash: order.getHash(), + status: OrderStatus.Fillable, + takerTokenFilledAmount: fillAmount, + }); + expect(fillableTakerAmount).to.bignumber.eq( + getActualFillableTakerTokenAmount(order, balance, allowance, fillAmount), + ); + expect(isSignatureValid).to.eq(true); + }); + }); + + async function batchFundOrderMakerAsync(orders: Array): Promise { + await makerToken.burn(maker, await makerToken.balanceOf(maker).callAsync()).awaitTransactionSuccessAsync(); + const balance = BigNumber.sum(...orders.map(o => o.makerAmount)); + await makerToken.mint(maker, balance).awaitTransactionSuccessAsync(); + await makerToken.approve(zeroEx.address, balance).awaitTransactionSuccessAsync({ from: maker }); + } + + describe('batchGetLimitOrderRelevantStates()', () => { + it('works with multiple orders', async () => { + const orders = new Array(3).fill(0).map(() => getTestLimitOrder()); + await batchFundOrderMakerAsync(orders); + const [orderInfos, fillableTakerAmounts, isSignatureValids] = await zeroEx + .batchGetLimitOrderRelevantStates( + orders, + await Promise.all(orders.map(async o => o.getSignatureWithProviderAsync(env.provider))), + ) + .callAsync(); + expect(orderInfos).to.be.length(orders.length); + expect(fillableTakerAmounts).to.be.length(orders.length); + expect(isSignatureValids).to.be.length(orders.length); + for (let i = 0; i < orders.length; ++i) { + expect(orderInfos[i]).to.deep.eq({ + orderHash: orders[i].getHash(), + status: OrderStatus.Fillable, + takerTokenFilledAmount: ZERO_AMOUNT, + }); + expect(fillableTakerAmounts[i]).to.bignumber.eq(orders[i].takerAmount); + expect(isSignatureValids[i]).to.eq(true); + } + }); + }); + + describe('batchGetRfqOrderRelevantStates()', () => { + it('works with multiple orders', async () => { + const orders = new Array(3).fill(0).map(() => getTestRfqOrder()); + await batchFundOrderMakerAsync(orders); + const [orderInfos, fillableTakerAmounts, isSignatureValids] = await zeroEx + .batchGetRfqOrderRelevantStates( + orders, + await Promise.all(orders.map(async o => o.getSignatureWithProviderAsync(env.provider))), + ) + .callAsync(); + expect(orderInfos).to.be.length(orders.length); + expect(fillableTakerAmounts).to.be.length(orders.length); + expect(isSignatureValids).to.be.length(orders.length); + for (let i = 0; i < orders.length; ++i) { + expect(orderInfos[i]).to.deep.eq({ + orderHash: orders[i].getHash(), + status: OrderStatus.Fillable, + takerTokenFilledAmount: ZERO_AMOUNT, + }); + expect(fillableTakerAmounts[i]).to.bignumber.eq(orders[i].takerAmount); + expect(isSignatureValids[i]).to.eq(true); + } + }); }); }); diff --git a/packages/contract-artifacts/CHANGELOG.json b/packages/contract-artifacts/CHANGELOG.json index 4ed201e4c8..8e87457311 100644 --- a/packages/contract-artifacts/CHANGELOG.json +++ b/packages/contract-artifacts/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "3.11.0", + "changes": [ + { + "note": "Update IZeroEx artifact", + "pr": 97 + } + ] + }, { "version": "3.10.0", "changes": [ diff --git a/packages/contract-artifacts/artifacts/IZeroEx.json b/packages/contract-artifacts/artifacts/IZeroEx.json index 83d46a4f7f..e490d5ebe3 100644 --- a/packages/contract-artifacts/artifacts/IZeroEx.json +++ b/packages/contract-artifacts/artifacts/IZeroEx.json @@ -457,6 +457,114 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [ + { + "components": [ + { "internalType": "contract IERC20TokenV06", "name": "makerToken", "type": "address" }, + { "internalType": "contract IERC20TokenV06", "name": "takerToken", "type": "address" }, + { "internalType": "uint128", "name": "makerAmount", "type": "uint128" }, + { "internalType": "uint128", "name": "takerAmount", "type": "uint128" }, + { "internalType": "uint128", "name": "takerTokenFeeAmount", "type": "uint128" }, + { "internalType": "address", "name": "maker", "type": "address" }, + { "internalType": "address", "name": "taker", "type": "address" }, + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "address", "name": "feeRecipient", "type": "address" }, + { "internalType": "bytes32", "name": "pool", "type": "bytes32" }, + { "internalType": "uint64", "name": "expiry", "type": "uint64" }, + { "internalType": "uint256", "name": "salt", "type": "uint256" } + ], + "internalType": "struct LibNativeOrder.LimitOrder[]", + "name": "orders", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "enum LibSignature.SignatureType", + "name": "signatureType", + "type": "uint8" + }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "internalType": "struct LibSignature.Signature[]", + "name": "signatures", + "type": "tuple[]" + } + ], + "name": "batchGetLimitOrderRelevantStates", + "outputs": [ + { + "components": [ + { "internalType": "bytes32", "name": "orderHash", "type": "bytes32" }, + { "internalType": "enum LibNativeOrder.OrderStatus", "name": "status", "type": "uint8" }, + { "internalType": "uint128", "name": "takerTokenFilledAmount", "type": "uint128" } + ], + "internalType": "struct LibNativeOrder.OrderInfo[]", + "name": "orderInfos", + "type": "tuple[]" + }, + { "internalType": "uint128[]", "name": "actualFillableTakerTokenAmounts", "type": "uint128[]" }, + { "internalType": "bool[]", "name": "isSignatureValids", "type": "bool[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "contract IERC20TokenV06", "name": "makerToken", "type": "address" }, + { "internalType": "contract IERC20TokenV06", "name": "takerToken", "type": "address" }, + { "internalType": "uint128", "name": "makerAmount", "type": "uint128" }, + { "internalType": "uint128", "name": "takerAmount", "type": "uint128" }, + { "internalType": "address", "name": "maker", "type": "address" }, + { "internalType": "address", "name": "taker", "type": "address" }, + { "internalType": "address", "name": "txOrigin", "type": "address" }, + { "internalType": "bytes32", "name": "pool", "type": "bytes32" }, + { "internalType": "uint64", "name": "expiry", "type": "uint64" }, + { "internalType": "uint256", "name": "salt", "type": "uint256" } + ], + "internalType": "struct LibNativeOrder.RfqOrder[]", + "name": "orders", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "enum LibSignature.SignatureType", + "name": "signatureType", + "type": "uint8" + }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "internalType": "struct LibSignature.Signature[]", + "name": "signatures", + "type": "tuple[]" + } + ], + "name": "batchGetRfqOrderRelevantStates", + "outputs": [ + { + "components": [ + { "internalType": "bytes32", "name": "orderHash", "type": "bytes32" }, + { "internalType": "enum LibNativeOrder.OrderStatus", "name": "status", "type": "uint8" }, + { "internalType": "uint128", "name": "takerTokenFilledAmount", "type": "uint128" } + ], + "internalType": "struct LibNativeOrder.OrderInfo[]", + "name": "orderInfos", + "type": "tuple[]" + }, + { "internalType": "uint128[]", "name": "actualFillableTakerTokenAmounts", "type": "uint128[]" }, + { "internalType": "bool[]", "name": "isSignatureValids", "type": "bool[]" } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -834,6 +942,61 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "components": [ + { "internalType": "contract IERC20TokenV06", "name": "makerToken", "type": "address" }, + { "internalType": "contract IERC20TokenV06", "name": "takerToken", "type": "address" }, + { "internalType": "uint128", "name": "makerAmount", "type": "uint128" }, + { "internalType": "uint128", "name": "takerAmount", "type": "uint128" }, + { "internalType": "uint128", "name": "takerTokenFeeAmount", "type": "uint128" }, + { "internalType": "address", "name": "maker", "type": "address" }, + { "internalType": "address", "name": "taker", "type": "address" }, + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "address", "name": "feeRecipient", "type": "address" }, + { "internalType": "bytes32", "name": "pool", "type": "bytes32" }, + { "internalType": "uint64", "name": "expiry", "type": "uint64" }, + { "internalType": "uint256", "name": "salt", "type": "uint256" } + ], + "internalType": "struct LibNativeOrder.LimitOrder", + "name": "order", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "enum LibSignature.SignatureType", + "name": "signatureType", + "type": "uint8" + }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "internalType": "struct LibSignature.Signature", + "name": "signature", + "type": "tuple" + } + ], + "name": "getLimitOrderRelevantState", + "outputs": [ + { + "components": [ + { "internalType": "bytes32", "name": "orderHash", "type": "bytes32" }, + { "internalType": "enum LibNativeOrder.OrderStatus", "name": "status", "type": "uint8" }, + { "internalType": "uint128", "name": "takerTokenFilledAmount", "type": "uint128" } + ], + "internalType": "struct LibNativeOrder.OrderInfo", + "name": "orderInfo", + "type": "tuple" + }, + { "internalType": "uint128", "name": "actualFillableTakerTokenAmount", "type": "uint128" }, + { "internalType": "bool", "name": "isSignatureValid", "type": "bool" } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -966,6 +1129,59 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "components": [ + { "internalType": "contract IERC20TokenV06", "name": "makerToken", "type": "address" }, + { "internalType": "contract IERC20TokenV06", "name": "takerToken", "type": "address" }, + { "internalType": "uint128", "name": "makerAmount", "type": "uint128" }, + { "internalType": "uint128", "name": "takerAmount", "type": "uint128" }, + { "internalType": "address", "name": "maker", "type": "address" }, + { "internalType": "address", "name": "taker", "type": "address" }, + { "internalType": "address", "name": "txOrigin", "type": "address" }, + { "internalType": "bytes32", "name": "pool", "type": "bytes32" }, + { "internalType": "uint64", "name": "expiry", "type": "uint64" }, + { "internalType": "uint256", "name": "salt", "type": "uint256" } + ], + "internalType": "struct LibNativeOrder.RfqOrder", + "name": "order", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "enum LibSignature.SignatureType", + "name": "signatureType", + "type": "uint8" + }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "internalType": "struct LibSignature.Signature", + "name": "signature", + "type": "tuple" + } + ], + "name": "getRfqOrderRelevantState", + "outputs": [ + { + "components": [ + { "internalType": "bytes32", "name": "orderHash", "type": "bytes32" }, + { "internalType": "enum LibNativeOrder.OrderStatus", "name": "status", "type": "uint8" }, + { "internalType": "uint128", "name": "takerTokenFilledAmount", "type": "uint128" } + ], + "internalType": "struct LibNativeOrder.OrderInfo", + "name": "orderInfo", + "type": "tuple" + }, + { "internalType": "uint128", "name": "actualFillableTakerTokenAmount", "type": "uint128" }, + { "internalType": "bool", "name": "isSignatureValid", "type": "bool" } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { "internalType": "bytes4", "name": "selector", "type": "bytes4" }, @@ -1230,6 +1446,24 @@ }, "returns": { "returnResults": "The ABI-encoded results of the underlying calls." } }, + "batchGetLimitOrderRelevantStates((address,address,uint128,uint128,uint128,address,address,address,address,bytes32,uint64,uint256)[],(uint8,uint8,bytes32,bytes32)[])": { + "details": "Batch version of `getLimitOrderRelevantState()`.", + "params": { "orders": "The limit orders.", "signatures": "The order signatures." }, + "returns": { + "actualFillableTakerTokenAmounts": "How much of each order is fillable based on maker funds, in taker tokens.", + "isSignatureValids": "Whether each signature is valid for the order.", + "orderInfos": "Info about the orders." + } + }, + "batchGetRfqOrderRelevantStates((address,address,uint128,uint128,address,address,address,bytes32,uint64,uint256)[],(uint8,uint8,bytes32,bytes32)[])": { + "details": "Batch version of `getRfqOrderRelevantState()`.", + "params": { "orders": "The RFQ orders.", "signatures": "The order signatures." }, + "returns": { + "actualFillableTakerTokenAmounts": "How much of each order is fillable based on maker funds, in taker tokens.", + "isSignatureValids": "Whether each signature is valid for the order.", + "orderInfos": "Info about the orders." + } + }, "cancelLimitOrder((address,address,uint128,uint128,uint128,address,address,address,address,bytes32,uint64,uint256))": { "details": "Cancel a single limit order. The caller must be the maker. Silently succeeds if the order has already been cancelled.", "params": { "order": "The limit order." } @@ -1326,6 +1560,15 @@ "params": { "order": "The limit order." }, "returns": { "orderInfo": "Info about the order." } }, + "getLimitOrderRelevantState((address,address,uint128,uint128,uint128,address,address,address,address,bytes32,uint64,uint256),(uint8,uint8,bytes32,bytes32))": { + "details": "Get order info, fillable amount, and signature validity for a limit order. Fillable amount is determined using balances and allowances of the maker.", + "params": { "order": "The limit order.", "signature": "The order signature." }, + "returns": { + "actualFillableTakerTokenAmount": "How much of the order is fillable based on maker funds, in taker tokens.", + "isSignatureValid": "Whether the signature is valid.", + "orderInfo": "Info about the order." + } + }, "getMetaTransactionExecutedBlock((address,address,uint256,uint256,uint256,uint256,bytes,uint256,address,uint256))": { "details": "Get the block at which a meta-transaction has been executed.", "params": { "mtx": "The meta-transaction." }, @@ -1359,6 +1602,15 @@ "params": { "order": "The RFQ order." }, "returns": { "orderInfo": "Info about the order." } }, + "getRfqOrderRelevantState((address,address,uint128,uint128,address,address,address,bytes32,uint64,uint256),(uint8,uint8,bytes32,bytes32))": { + "details": "Get order info, fillable amount, and signature validity for an RFQ order. Fillable amount is determined using balances and allowances of the maker.", + "params": { "order": "The RFQ order.", "signature": "The order signature." }, + "returns": { + "actualFillableTakerTokenAmount": "How much of the order is fillable based on maker funds, in taker tokens.", + "isSignatureValid": "Whether the signature is valid.", + "orderInfo": "Info about the order." + } + }, "getRollbackEntryAtIndex(bytes4,uint256)": { "details": "Retrieve an entry in the rollback history for a function.", "params": { "idx": "The index in the rollback history.", "selector": "The function selector." }, diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index ffe0a1482f..1a4b1df799 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "13.12.0", + "changes": [ + { + "note": "Update IZeroExContract wrapper", + "pr": 97 + } + ] + }, { "timestamp": 1608692071, "version": "13.11.2", diff --git a/packages/contract-wrappers/src/generated-wrappers/i_zero_ex.ts b/packages/contract-wrappers/src/generated-wrappers/i_zero_ex.ts index 638a2864ac..e043252504 100644 --- a/packages/contract-wrappers/src/generated-wrappers/i_zero_ex.ts +++ b/packages/contract-wrappers/src/generated-wrappers/i_zero_ex.ts @@ -1229,6 +1229,220 @@ export class IZeroExContract extends BaseContract { stateMutability: 'payable', type: 'function', }, + { + inputs: [ + { + name: 'orders', + type: 'tuple[]', + components: [ + { + name: 'makerToken', + type: 'address', + }, + { + name: 'takerToken', + type: 'address', + }, + { + name: 'makerAmount', + type: 'uint128', + }, + { + name: 'takerAmount', + type: 'uint128', + }, + { + name: 'takerTokenFeeAmount', + type: 'uint128', + }, + { + name: 'maker', + type: 'address', + }, + { + name: 'taker', + type: 'address', + }, + { + name: 'sender', + type: 'address', + }, + { + name: 'feeRecipient', + type: 'address', + }, + { + name: 'pool', + type: 'bytes32', + }, + { + name: 'expiry', + type: 'uint64', + }, + { + name: 'salt', + type: 'uint256', + }, + ], + }, + { + name: 'signatures', + type: 'tuple[]', + components: [ + { + name: 'signatureType', + type: 'uint8', + }, + { + name: 'v', + type: 'uint8', + }, + { + name: 'r', + type: 'bytes32', + }, + { + name: 's', + type: 'bytes32', + }, + ], + }, + ], + name: 'batchGetLimitOrderRelevantStates', + outputs: [ + { + name: 'orderInfos', + type: 'tuple[]', + components: [ + { + name: 'orderHash', + type: 'bytes32', + }, + { + name: 'status', + type: 'uint8', + }, + { + name: 'takerTokenFilledAmount', + type: 'uint128', + }, + ], + }, + { + name: 'actualFillableTakerTokenAmounts', + type: 'uint128[]', + }, + { + name: 'isSignatureValids', + type: 'bool[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + name: 'orders', + type: 'tuple[]', + components: [ + { + name: 'makerToken', + type: 'address', + }, + { + name: 'takerToken', + type: 'address', + }, + { + name: 'makerAmount', + type: 'uint128', + }, + { + name: 'takerAmount', + type: 'uint128', + }, + { + name: 'maker', + type: 'address', + }, + { + name: 'taker', + type: 'address', + }, + { + name: 'txOrigin', + type: 'address', + }, + { + name: 'pool', + type: 'bytes32', + }, + { + name: 'expiry', + type: 'uint64', + }, + { + name: 'salt', + type: 'uint256', + }, + ], + }, + { + name: 'signatures', + type: 'tuple[]', + components: [ + { + name: 'signatureType', + type: 'uint8', + }, + { + name: 'v', + type: 'uint8', + }, + { + name: 'r', + type: 'bytes32', + }, + { + name: 's', + type: 'bytes32', + }, + ], + }, + ], + name: 'batchGetRfqOrderRelevantStates', + outputs: [ + { + name: 'orderInfos', + type: 'tuple[]', + components: [ + { + name: 'orderHash', + type: 'bytes32', + }, + { + name: 'status', + type: 'uint8', + }, + { + name: 'takerTokenFilledAmount', + type: 'uint128', + }, + ], + }, + { + name: 'actualFillableTakerTokenAmounts', + type: 'uint128[]', + }, + { + name: 'isSignatureValids', + type: 'bool[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, { inputs: [ { @@ -2018,6 +2232,117 @@ export class IZeroExContract extends BaseContract { stateMutability: 'view', type: 'function', }, + { + inputs: [ + { + name: 'order', + type: 'tuple', + components: [ + { + name: 'makerToken', + type: 'address', + }, + { + name: 'takerToken', + type: 'address', + }, + { + name: 'makerAmount', + type: 'uint128', + }, + { + name: 'takerAmount', + type: 'uint128', + }, + { + name: 'takerTokenFeeAmount', + type: 'uint128', + }, + { + name: 'maker', + type: 'address', + }, + { + name: 'taker', + type: 'address', + }, + { + name: 'sender', + type: 'address', + }, + { + name: 'feeRecipient', + type: 'address', + }, + { + name: 'pool', + type: 'bytes32', + }, + { + name: 'expiry', + type: 'uint64', + }, + { + name: 'salt', + type: 'uint256', + }, + ], + }, + { + name: 'signature', + type: 'tuple', + components: [ + { + name: 'signatureType', + type: 'uint8', + }, + { + name: 'v', + type: 'uint8', + }, + { + name: 'r', + type: 'bytes32', + }, + { + name: 's', + type: 'bytes32', + }, + ], + }, + ], + name: 'getLimitOrderRelevantState', + outputs: [ + { + name: 'orderInfo', + type: 'tuple', + components: [ + { + name: 'orderHash', + type: 'bytes32', + }, + { + name: 'status', + type: 'uint8', + }, + { + name: 'takerTokenFilledAmount', + type: 'uint128', + }, + ], + }, + { + name: 'actualFillableTakerTokenAmount', + type: 'uint128', + }, + { + name: 'isSignatureValid', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, { inputs: [ { @@ -2309,6 +2634,109 @@ export class IZeroExContract extends BaseContract { stateMutability: 'view', type: 'function', }, + { + inputs: [ + { + name: 'order', + type: 'tuple', + components: [ + { + name: 'makerToken', + type: 'address', + }, + { + name: 'takerToken', + type: 'address', + }, + { + name: 'makerAmount', + type: 'uint128', + }, + { + name: 'takerAmount', + type: 'uint128', + }, + { + name: 'maker', + type: 'address', + }, + { + name: 'taker', + type: 'address', + }, + { + name: 'txOrigin', + type: 'address', + }, + { + name: 'pool', + type: 'bytes32', + }, + { + name: 'expiry', + type: 'uint64', + }, + { + name: 'salt', + type: 'uint256', + }, + ], + }, + { + name: 'signature', + type: 'tuple', + components: [ + { + name: 'signatureType', + type: 'uint8', + }, + { + name: 'v', + type: 'uint8', + }, + { + name: 'r', + type: 'bytes32', + }, + { + name: 's', + type: 'bytes32', + }, + ], + }, + ], + name: 'getRfqOrderRelevantState', + outputs: [ + { + name: 'orderInfo', + type: 'tuple', + components: [ + { + name: 'orderHash', + type: 'bytes32', + }, + { + name: 'status', + type: 'uint8', + }, + { + name: 'takerTokenFilledAmount', + type: 'uint128', + }, + ], + }, + { + name: 'actualFillableTakerTokenAmount', + type: 'uint128', + }, + { + name: 'isSignatureValid', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, { inputs: [ { @@ -3429,6 +3857,178 @@ export class IZeroExContract extends BaseContract { }, }; } + /** + * Batch version of `getLimitOrderRelevantState()`. + * @param orders The limit orders. + * @param signatures The order signatures. + */ + public batchGetLimitOrderRelevantStates( + orders: Array<{ + makerToken: string; + takerToken: string; + makerAmount: BigNumber; + takerAmount: BigNumber; + takerTokenFeeAmount: BigNumber; + maker: string; + taker: string; + sender: string; + feeRecipient: string; + pool: string; + expiry: BigNumber; + salt: BigNumber; + }>, + signatures: Array<{ signatureType: number | BigNumber; v: number | BigNumber; r: string; s: string }>, + ): ContractTxFunctionObj< + [Array<{ orderHash: string; status: number; takerTokenFilledAmount: BigNumber }>, BigNumber[], boolean[]] + > { + const self = (this as any) as IZeroExContract; + assert.isArray('orders', orders); + assert.isArray('signatures', signatures); + const functionSignature = + 'batchGetLimitOrderRelevantStates((address,address,uint128,uint128,uint128,address,address,address,address,bytes32,uint64,uint256)[],(uint8,uint8,bytes32,bytes32)[])'; + + return { + async sendTransactionAsync( + txData?: Partial | undefined, + opts: SendTransactionOpts = { shouldValidate: true }, + ): Promise { + const txDataWithDefaults = await self._applyDefaultsToTxDataAsync( + { data: this.getABIEncodedTransactionData(), ...txData }, + this.estimateGasAsync.bind(this), + ); + if (opts.shouldValidate !== false) { + await this.callAsync(txDataWithDefaults); + } + return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults); + }, + awaitTransactionSuccessAsync( + txData?: Partial, + opts: AwaitTransactionSuccessOpts = { shouldValidate: true }, + ): PromiseWithTransactionHash { + return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts); + }, + async estimateGasAsync(txData?: Partial | undefined): Promise { + const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({ + data: this.getABIEncodedTransactionData(), + ...txData, + }); + return self._web3Wrapper.estimateGasAsync(txDataWithDefaults); + }, + async callAsync( + callData: Partial = {}, + defaultBlock?: BlockParam, + ): Promise< + [ + Array<{ orderHash: string; status: number; takerTokenFilledAmount: BigNumber }>, + BigNumber[], + boolean[] + ] + > { + BaseContract._assertCallParams(callData, defaultBlock); + const rawCallResult = await self._performCallAsync( + { data: this.getABIEncodedTransactionData(), ...callData }, + defaultBlock, + ); + const abiEncoder = self._lookupAbiEncoder(functionSignature); + BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder); + return abiEncoder.strictDecodeReturnValue< + [ + Array<{ orderHash: string; status: number; takerTokenFilledAmount: BigNumber }>, + BigNumber[], + boolean[] + ] + >(rawCallResult); + }, + getABIEncodedTransactionData(): string { + return self._strictEncodeArguments(functionSignature, [orders, signatures]); + }, + }; + } + /** + * Batch version of `getRfqOrderRelevantState()`. + * @param orders The RFQ orders. + * @param signatures The order signatures. + */ + public batchGetRfqOrderRelevantStates( + orders: Array<{ + makerToken: string; + takerToken: string; + makerAmount: BigNumber; + takerAmount: BigNumber; + maker: string; + taker: string; + txOrigin: string; + pool: string; + expiry: BigNumber; + salt: BigNumber; + }>, + signatures: Array<{ signatureType: number | BigNumber; v: number | BigNumber; r: string; s: string }>, + ): ContractTxFunctionObj< + [Array<{ orderHash: string; status: number; takerTokenFilledAmount: BigNumber }>, BigNumber[], boolean[]] + > { + const self = (this as any) as IZeroExContract; + assert.isArray('orders', orders); + assert.isArray('signatures', signatures); + const functionSignature = + 'batchGetRfqOrderRelevantStates((address,address,uint128,uint128,address,address,address,bytes32,uint64,uint256)[],(uint8,uint8,bytes32,bytes32)[])'; + + return { + async sendTransactionAsync( + txData?: Partial | undefined, + opts: SendTransactionOpts = { shouldValidate: true }, + ): Promise { + const txDataWithDefaults = await self._applyDefaultsToTxDataAsync( + { data: this.getABIEncodedTransactionData(), ...txData }, + this.estimateGasAsync.bind(this), + ); + if (opts.shouldValidate !== false) { + await this.callAsync(txDataWithDefaults); + } + return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults); + }, + awaitTransactionSuccessAsync( + txData?: Partial, + opts: AwaitTransactionSuccessOpts = { shouldValidate: true }, + ): PromiseWithTransactionHash { + return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts); + }, + async estimateGasAsync(txData?: Partial | undefined): Promise { + const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({ + data: this.getABIEncodedTransactionData(), + ...txData, + }); + return self._web3Wrapper.estimateGasAsync(txDataWithDefaults); + }, + async callAsync( + callData: Partial = {}, + defaultBlock?: BlockParam, + ): Promise< + [ + Array<{ orderHash: string; status: number; takerTokenFilledAmount: BigNumber }>, + BigNumber[], + boolean[] + ] + > { + BaseContract._assertCallParams(callData, defaultBlock); + const rawCallResult = await self._performCallAsync( + { data: this.getABIEncodedTransactionData(), ...callData }, + defaultBlock, + ); + const abiEncoder = self._lookupAbiEncoder(functionSignature); + BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder); + return abiEncoder.strictDecodeReturnValue< + [ + Array<{ orderHash: string; status: number; takerTokenFilledAmount: BigNumber }>, + BigNumber[], + boolean[] + ] + >(rawCallResult); + }, + getABIEncodedTransactionData(): string { + return self._strictEncodeArguments(functionSignature, [orders, signatures]); + }, + }; + } /** * Cancel a single limit order. The caller must be the maker. * Silently succeeds if the order has already been cancelled. @@ -4346,6 +4946,83 @@ export class IZeroExContract extends BaseContract { }, }; } + /** + * Get order info, fillable amount, and signature validity for a limit order. + * Fillable amount is determined using balances and allowances of the maker. + * @param order The limit order. + * @param signature The order signature. + */ + public getLimitOrderRelevantState( + order: { + makerToken: string; + takerToken: string; + makerAmount: BigNumber; + takerAmount: BigNumber; + takerTokenFeeAmount: BigNumber; + maker: string; + taker: string; + sender: string; + feeRecipient: string; + pool: string; + expiry: BigNumber; + salt: BigNumber; + }, + signature: { signatureType: number | BigNumber; v: number | BigNumber; r: string; s: string }, + ): ContractTxFunctionObj< + [{ orderHash: string; status: number; takerTokenFilledAmount: BigNumber }, BigNumber, boolean] + > { + const self = (this as any) as IZeroExContract; + + const functionSignature = + 'getLimitOrderRelevantState((address,address,uint128,uint128,uint128,address,address,address,address,bytes32,uint64,uint256),(uint8,uint8,bytes32,bytes32))'; + + return { + async sendTransactionAsync( + txData?: Partial | undefined, + opts: SendTransactionOpts = { shouldValidate: true }, + ): Promise { + const txDataWithDefaults = await self._applyDefaultsToTxDataAsync( + { data: this.getABIEncodedTransactionData(), ...txData }, + this.estimateGasAsync.bind(this), + ); + if (opts.shouldValidate !== false) { + await this.callAsync(txDataWithDefaults); + } + return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults); + }, + awaitTransactionSuccessAsync( + txData?: Partial, + opts: AwaitTransactionSuccessOpts = { shouldValidate: true }, + ): PromiseWithTransactionHash { + return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts); + }, + async estimateGasAsync(txData?: Partial | undefined): Promise { + const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({ + data: this.getABIEncodedTransactionData(), + ...txData, + }); + return self._web3Wrapper.estimateGasAsync(txDataWithDefaults); + }, + async callAsync( + callData: Partial = {}, + defaultBlock?: BlockParam, + ): Promise<[{ orderHash: string; status: number; takerTokenFilledAmount: BigNumber }, BigNumber, boolean]> { + BaseContract._assertCallParams(callData, defaultBlock); + const rawCallResult = await self._performCallAsync( + { data: this.getABIEncodedTransactionData(), ...callData }, + defaultBlock, + ); + const abiEncoder = self._lookupAbiEncoder(functionSignature); + BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder); + return abiEncoder.strictDecodeReturnValue< + [{ orderHash: string; status: number; takerTokenFilledAmount: BigNumber }, BigNumber, boolean] + >(rawCallResult); + }, + getABIEncodedTransactionData(): string { + return self._strictEncodeArguments(functionSignature, [order, signature]); + }, + }; + } /** * Get the block at which a meta-transaction has been executed. * @param mtx The meta-transaction. @@ -4755,6 +5432,81 @@ export class IZeroExContract extends BaseContract { }, }; } + /** + * Get order info, fillable amount, and signature validity for an RFQ order. + * Fillable amount is determined using balances and allowances of the maker. + * @param order The RFQ order. + * @param signature The order signature. + */ + public getRfqOrderRelevantState( + order: { + makerToken: string; + takerToken: string; + makerAmount: BigNumber; + takerAmount: BigNumber; + maker: string; + taker: string; + txOrigin: string; + pool: string; + expiry: BigNumber; + salt: BigNumber; + }, + signature: { signatureType: number | BigNumber; v: number | BigNumber; r: string; s: string }, + ): ContractTxFunctionObj< + [{ orderHash: string; status: number; takerTokenFilledAmount: BigNumber }, BigNumber, boolean] + > { + const self = (this as any) as IZeroExContract; + + const functionSignature = + 'getRfqOrderRelevantState((address,address,uint128,uint128,address,address,address,bytes32,uint64,uint256),(uint8,uint8,bytes32,bytes32))'; + + return { + async sendTransactionAsync( + txData?: Partial | undefined, + opts: SendTransactionOpts = { shouldValidate: true }, + ): Promise { + const txDataWithDefaults = await self._applyDefaultsToTxDataAsync( + { data: this.getABIEncodedTransactionData(), ...txData }, + this.estimateGasAsync.bind(this), + ); + if (opts.shouldValidate !== false) { + await this.callAsync(txDataWithDefaults); + } + return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults); + }, + awaitTransactionSuccessAsync( + txData?: Partial, + opts: AwaitTransactionSuccessOpts = { shouldValidate: true }, + ): PromiseWithTransactionHash { + return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts); + }, + async estimateGasAsync(txData?: Partial | undefined): Promise { + const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({ + data: this.getABIEncodedTransactionData(), + ...txData, + }); + return self._web3Wrapper.estimateGasAsync(txDataWithDefaults); + }, + async callAsync( + callData: Partial = {}, + defaultBlock?: BlockParam, + ): Promise<[{ orderHash: string; status: number; takerTokenFilledAmount: BigNumber }, BigNumber, boolean]> { + BaseContract._assertCallParams(callData, defaultBlock); + const rawCallResult = await self._performCallAsync( + { data: this.getABIEncodedTransactionData(), ...callData }, + defaultBlock, + ); + const abiEncoder = self._lookupAbiEncoder(functionSignature); + BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder); + return abiEncoder.strictDecodeReturnValue< + [{ orderHash: string; status: number; takerTokenFilledAmount: BigNumber }, BigNumber, boolean] + >(rawCallResult); + }, + getABIEncodedTransactionData(): string { + return self._strictEncodeArguments(functionSignature, [order, signature]); + }, + }; + } /** * Retrieve an entry in the rollback history for a function. * @param selector The function selector.