Merge branch 'development' into feature/instant/failure-state
This commit is contained in:
@@ -26,3 +26,4 @@ packages/subproviders/ @fabioberger @dekz
|
||||
packages/connect/ @fragosti
|
||||
packages/monorepo-scripts/ @fabioberger
|
||||
packages/order-utils/ @fabioberger @LogvinovLeon
|
||||
python-packages/ @feuGeneA
|
||||
@@ -25,6 +25,10 @@
|
||||
{
|
||||
"note": "Add missing types to public interface",
|
||||
"pr": 1139
|
||||
},
|
||||
{
|
||||
"note": "Throw `SignatureRequestDenied` and `TransactionValueTooLow` errors when executing buy",
|
||||
"pr": 1147
|
||||
}
|
||||
],
|
||||
"timestamp": 1539871071
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ContractWrappers } from '@0x/contract-wrappers';
|
||||
import { ContractWrappers, ContractWrappersError, ForwarderWrapperError } from '@0x/contract-wrappers';
|
||||
import { schemas } from '@0x/json-schemas';
|
||||
import { SignedOrder } from '@0x/order-utils';
|
||||
import { ObjectMap } from '@0x/types';
|
||||
@@ -210,21 +210,32 @@ export class AssetBuyer {
|
||||
throw new Error(AssetBuyerError.NoAddressAvailable);
|
||||
}
|
||||
}
|
||||
// if no ethAmount is provided, default to the worst ethAmount from buyQuote
|
||||
const txHash = await this._contractWrappers.forwarder.marketBuyOrdersWithEthAsync(
|
||||
orders,
|
||||
assetBuyAmount,
|
||||
finalTakerAddress,
|
||||
ethAmount || worstCaseQuoteInfo.totalEthAmount,
|
||||
feeOrders,
|
||||
feePercentage,
|
||||
feeRecipient,
|
||||
{
|
||||
gasLimit,
|
||||
gasPrice,
|
||||
},
|
||||
);
|
||||
return txHash;
|
||||
try {
|
||||
// if no ethAmount is provided, default to the worst ethAmount from buyQuote
|
||||
const txHash = await this._contractWrappers.forwarder.marketBuyOrdersWithEthAsync(
|
||||
orders,
|
||||
assetBuyAmount,
|
||||
finalTakerAddress,
|
||||
ethAmount || worstCaseQuoteInfo.totalEthAmount,
|
||||
feeOrders,
|
||||
feePercentage,
|
||||
feeRecipient,
|
||||
{
|
||||
gasLimit,
|
||||
gasPrice,
|
||||
shouldValidate: true,
|
||||
},
|
||||
);
|
||||
return txHash;
|
||||
} catch (err) {
|
||||
if (_.includes(err.message, ContractWrappersError.SignatureRequestDenied)) {
|
||||
throw new Error(AssetBuyerError.SignatureRequestDenied);
|
||||
} else if (_.includes(err.message, ForwarderWrapperError.CompleteFillFailed)) {
|
||||
throw new Error(AssetBuyerError.TransactionValueTooLow);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Grab orders from the map, if there is a miss or it is time to refresh, fetch and process the orders
|
||||
|
||||
@@ -112,6 +112,8 @@ export enum AssetBuyerError {
|
||||
NoAddressAvailable = 'NO_ADDRESS_AVAILABLE',
|
||||
InvalidOrderProviderResponse = 'INVALID_ORDER_PROVIDER_RESPONSE',
|
||||
AssetUnavailable = 'ASSET_UNAVAILABLE',
|
||||
SignatureRequestDenied = 'SIGNATURE_REQUEST_DENIED',
|
||||
TransactionValueTooLow = 'TRANSACTION_VALUE_TOO_LOW',
|
||||
}
|
||||
|
||||
export interface OrdersAndFillableAmounts {
|
||||
|
||||
@@ -37,6 +37,14 @@
|
||||
"note":
|
||||
"Removed ContractNotFound errors. Checking for this error was somewhat ineffecient. Relevant methods/functions now return the default error from web3-wrapper, which we feel provides enough information.",
|
||||
"pr": 1105
|
||||
},
|
||||
{
|
||||
"note": "Add `ForwarderWrapperError` to public interface",
|
||||
"pr": 1147
|
||||
},
|
||||
{
|
||||
"note": "Add `ContractWrapperError.SignatureRequestDenied` to public interface",
|
||||
"pr": 1147
|
||||
}
|
||||
],
|
||||
"timestamp": 1539871071
|
||||
|
||||
@@ -39,6 +39,7 @@ export { TransactionEncoder } from './utils/transaction_encoder';
|
||||
|
||||
export {
|
||||
ContractWrappersError,
|
||||
ForwarderWrapperError,
|
||||
IndexedFilterValues,
|
||||
BlockRange,
|
||||
ContractWrappersConfig,
|
||||
|
||||
@@ -18,6 +18,10 @@ export enum ExchangeWrapperError {
|
||||
AssetDataMismatch = 'ASSET_DATA_MISMATCH',
|
||||
}
|
||||
|
||||
export enum ForwarderWrapperError {
|
||||
CompleteFillFailed = 'COMPLETE_FILL_FAILED',
|
||||
}
|
||||
|
||||
export enum ContractWrappersError {
|
||||
ContractNotDeployedOnNetwork = 'CONTRACT_NOT_DEPLOYED_ON_NETWORK',
|
||||
InsufficientAllowanceForTransfer = 'INSUFFICIENT_ALLOWANCE_FOR_TRANSFER',
|
||||
@@ -30,6 +34,7 @@ export enum ContractWrappersError {
|
||||
SubscriptionAlreadyPresent = 'SUBSCRIPTION_ALREADY_PRESENT',
|
||||
ERC721OwnerNotFound = 'ERC_721_OWNER_NOT_FOUND',
|
||||
ERC721NoApproval = 'ERC_721_NO_APPROVAL',
|
||||
SignatureRequestDenied = 'SIGNATURE_REQUEST_DENIED',
|
||||
}
|
||||
|
||||
export enum InternalContractWrappersError {
|
||||
|
||||
@@ -14,4 +14,5 @@ export const constants = {
|
||||
ZERO_AMOUNT: new BigNumber(0),
|
||||
ONE_AMOUNT: new BigNumber(1),
|
||||
ETHER_TOKEN_DECIMALS: 18,
|
||||
USER_DENIED_SIGNATURE_PATTERN: 'User denied transaction signature',
|
||||
};
|
||||
|
||||
@@ -29,6 +29,14 @@ const schemaErrorTransformer = (error: Error) => {
|
||||
return error;
|
||||
};
|
||||
|
||||
const signatureRequestErrorTransformer = (error: Error) => {
|
||||
if (_.includes(error.message, constants.USER_DENIED_SIGNATURE_PATTERN)) {
|
||||
const errMsg = ContractWrappersError.SignatureRequestDenied;
|
||||
return new Error(errMsg);
|
||||
}
|
||||
return error;
|
||||
};
|
||||
|
||||
/**
|
||||
* Source: https://stackoverflow.com/a/29837695/3546986
|
||||
*/
|
||||
@@ -87,7 +95,11 @@ const syncErrorHandlerFactory = (errorTransformer: ErrorTransformer) => {
|
||||
};
|
||||
|
||||
// _.flow(f, g) = f ∘ g
|
||||
const zeroExErrorTransformer = _.flow(schemaErrorTransformer, contractCallErrorTransformer);
|
||||
const zeroExErrorTransformer = _.flow(
|
||||
schemaErrorTransformer,
|
||||
contractCallErrorTransformer,
|
||||
signatureRequestErrorTransformer,
|
||||
);
|
||||
|
||||
export const decorators = {
|
||||
asyncZeroExErrorHandler: asyncErrorHandlerFactory(zeroExErrorTransformer),
|
||||
|
||||
@@ -155,8 +155,10 @@ contract MixinExchangeWrapper is
|
||||
uint256 remainingMakerAssetFillAmount = safeSub(makerAssetFillAmount, totalFillResults.makerAssetFilledAmount);
|
||||
|
||||
// Convert the remaining amount of makerAsset to buy into remaining amount
|
||||
// of takerAsset to sell, assuming entire amount can be sold in the current order
|
||||
uint256 remainingTakerAssetFillAmount = getPartialAmountFloor(
|
||||
// of takerAsset to sell, assuming entire amount can be sold in the current order.
|
||||
// We round up because the exchange rate computed by fillOrder rounds in favor
|
||||
// of the Maker. In this case we want to overestimate the amount of takerAsset.
|
||||
uint256 remainingTakerAssetFillAmount = getPartialAmountCeil(
|
||||
orders[i].takerAssetAmount,
|
||||
orders[i].makerAssetAmount,
|
||||
remainingMakerAssetFillAmount
|
||||
@@ -224,7 +226,9 @@ contract MixinExchangeWrapper is
|
||||
|
||||
// Convert the remaining amount of ZRX to buy into remaining amount
|
||||
// of WETH to sell, assuming entire amount can be sold in the current order.
|
||||
uint256 remainingWethSellAmount = getPartialAmountFloor(
|
||||
// We round up because the exchange rate computed by fillOrder rounds in favor
|
||||
// of the Maker. In this case we want to overestimate the amount of takerAsset.
|
||||
uint256 remainingWethSellAmount = getPartialAmountCeil(
|
||||
orders[i].takerAssetAmount,
|
||||
safeSub(orders[i].makerAssetAmount, orders[i].takerFee), // our exchange rate after fees
|
||||
remainingZrxBuyAmount
|
||||
@@ -233,7 +237,7 @@ contract MixinExchangeWrapper is
|
||||
// Attempt to sell the remaining amount of WETH.
|
||||
FillResults memory singleFillResult = fillOrderNoThrow(
|
||||
orders[i],
|
||||
safeAdd(remainingWethSellAmount, 1), // we add 1 wei to the fill amount to make up for rounding errors
|
||||
remainingWethSellAmount,
|
||||
signatures[i]
|
||||
);
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ describe(ContractName.Forwarder, () => {
|
||||
|
||||
let weth: DummyERC20TokenContract;
|
||||
let zrxToken: DummyERC20TokenContract;
|
||||
let erc20TokenA: DummyERC20TokenContract;
|
||||
let erc721Token: DummyERC721TokenContract;
|
||||
let forwarderContract: ForwarderContract;
|
||||
let wethContract: WETH9Contract;
|
||||
@@ -77,7 +78,6 @@ describe(ContractName.Forwarder, () => {
|
||||
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
|
||||
|
||||
const numDummyErc20ToDeploy = 3;
|
||||
let erc20TokenA;
|
||||
[erc20TokenA, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
|
||||
numDummyErc20ToDeploy,
|
||||
constants.DUMMY_TOKEN_DECIMALS,
|
||||
@@ -902,6 +902,269 @@ describe(ContractName.Forwarder, () => {
|
||||
);
|
||||
expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
it('Should buy slightly greater MakerAsset when exchange rate is rounded', async () => {
|
||||
// The 0x Protocol contracts round the exchange rate in favor of the Maker.
|
||||
// In this case, the taker must round up how much they're going to spend, which
|
||||
// in turn increases the amount of MakerAsset being purchased.
|
||||
// Example:
|
||||
// The taker wants to buy 5 units of the MakerAsset at a rate of 3M/2T.
|
||||
// For every 2 units of TakerAsset, the taker will receive 3 units of MakerAsset.
|
||||
// To purchase 5 units, the taker must spend 10/3 = 3.33 units of TakerAssset.
|
||||
// However, the Taker can only spend whole units.
|
||||
// Spending floor(10/3) = 3 units will yield a profit of Floor(3*3/2) = Floor(4.5) = 4 units of MakerAsset.
|
||||
// Spending ceil(10/3) = 4 units will yield a profit of Floor(4*3/2) = 6 units of MakerAsset.
|
||||
//
|
||||
// The forwarding contract will opt for the second option, which overbuys, to ensure the taker
|
||||
// receives at least the amount of MakerAsset they requested.
|
||||
//
|
||||
// Construct test case using values from example above
|
||||
orderWithoutFee = await orderFactory.newSignedOrderAsync({
|
||||
makerAssetAmount: new BigNumber('30'),
|
||||
takerAssetAmount: new BigNumber('20'),
|
||||
makerAssetData: assetDataUtils.encodeERC20AssetData(erc20TokenA.address),
|
||||
takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address),
|
||||
makerFee: new BigNumber(0),
|
||||
takerFee: new BigNumber(0),
|
||||
});
|
||||
const ordersWithoutFee = [orderWithoutFee];
|
||||
const feeOrders: SignedOrder[] = [];
|
||||
const desiredMakerAssetFillAmount = new BigNumber('5');
|
||||
const makerAssetFillAmount = new BigNumber('6');
|
||||
const ethValue = new BigNumber('4');
|
||||
// Execute test case
|
||||
tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(
|
||||
ordersWithoutFee,
|
||||
feeOrders,
|
||||
desiredMakerAssetFillAmount,
|
||||
{
|
||||
value: ethValue,
|
||||
from: takerAddress,
|
||||
},
|
||||
);
|
||||
// Fetch end balances and construct expected outputs
|
||||
const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
|
||||
const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
|
||||
const newBalances = await erc20Wrapper.getBalancesAsync();
|
||||
const primaryTakerAssetFillAmount = ethValue;
|
||||
const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
|
||||
// Validate test case
|
||||
expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount);
|
||||
expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
|
||||
expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
|
||||
constants.ZERO_AMOUNT,
|
||||
);
|
||||
expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
it('Should buy slightly greater MakerAsset when exchange rate is rounded, and MakerAsset is ZRX', async () => {
|
||||
// See the test case above for a detailed description of this case.
|
||||
// The difference here is that the MakerAsset is ZRX. We expect the same result as above,
|
||||
// but this tests a different code path.
|
||||
//
|
||||
// Construct test case using values from example above
|
||||
orderWithoutFee = await orderFactory.newSignedOrderAsync({
|
||||
makerAssetAmount: new BigNumber('30'),
|
||||
takerAssetAmount: new BigNumber('20'),
|
||||
makerAssetData: zrxAssetData,
|
||||
takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address),
|
||||
makerFee: new BigNumber(0),
|
||||
takerFee: new BigNumber(0),
|
||||
});
|
||||
const ordersWithoutFee = [orderWithoutFee];
|
||||
const feeOrders: SignedOrder[] = [];
|
||||
const desiredMakerAssetFillAmount = new BigNumber('5');
|
||||
const makerAssetFillAmount = new BigNumber('6');
|
||||
const ethValue = new BigNumber('4');
|
||||
// Execute test case
|
||||
tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(
|
||||
ordersWithoutFee,
|
||||
feeOrders,
|
||||
desiredMakerAssetFillAmount,
|
||||
{
|
||||
value: ethValue,
|
||||
from: takerAddress,
|
||||
},
|
||||
);
|
||||
// Fetch end balances and construct expected outputs
|
||||
const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
|
||||
const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
|
||||
const newBalances = await erc20Wrapper.getBalancesAsync();
|
||||
const primaryTakerAssetFillAmount = ethValue;
|
||||
const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
|
||||
// Validate test case
|
||||
expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount);
|
||||
expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
|
||||
expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
it('Should buy slightly greater MakerAsset when exchange rate is rounded (Regression Test)', async () => {
|
||||
// Order taken from a transaction on mainnet that failed due to a rounding error.
|
||||
orderWithoutFee = await orderFactory.newSignedOrderAsync({
|
||||
makerAssetAmount: new BigNumber('268166666666666666666'),
|
||||
takerAssetAmount: new BigNumber('219090625878836371'),
|
||||
makerAssetData: assetDataUtils.encodeERC20AssetData(erc20TokenA.address),
|
||||
takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address),
|
||||
makerFee: new BigNumber(0),
|
||||
takerFee: new BigNumber(0),
|
||||
});
|
||||
const ordersWithoutFee = [orderWithoutFee];
|
||||
const feeOrders: SignedOrder[] = [];
|
||||
// The taker will receive more than the desired amount of makerAsset due to rounding
|
||||
const desiredMakerAssetFillAmount = new BigNumber('5000000000000000000');
|
||||
const ethValue = new BigNumber('4084971271824171');
|
||||
const makerAssetFillAmount = ethValue
|
||||
.times(orderWithoutFee.makerAssetAmount)
|
||||
.dividedToIntegerBy(orderWithoutFee.takerAssetAmount);
|
||||
// Execute test case
|
||||
tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(
|
||||
ordersWithoutFee,
|
||||
feeOrders,
|
||||
desiredMakerAssetFillAmount,
|
||||
{
|
||||
value: ethValue,
|
||||
from: takerAddress,
|
||||
},
|
||||
);
|
||||
// Fetch end balances and construct expected outputs
|
||||
const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
|
||||
const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
|
||||
const newBalances = await erc20Wrapper.getBalancesAsync();
|
||||
const primaryTakerAssetFillAmount = ethValue;
|
||||
const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
|
||||
// Validate test case
|
||||
expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount);
|
||||
expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
|
||||
expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
|
||||
constants.ZERO_AMOUNT,
|
||||
);
|
||||
expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
it('Should buy slightly greater MakerAsset when exchange rate is rounded, and MakerAsset is ZRX (Regression Test)', async () => {
|
||||
// Order taken from a transaction on mainnet that failed due to a rounding error.
|
||||
orderWithoutFee = await orderFactory.newSignedOrderAsync({
|
||||
makerAssetAmount: new BigNumber('268166666666666666666'),
|
||||
takerAssetAmount: new BigNumber('219090625878836371'),
|
||||
makerAssetData: zrxAssetData,
|
||||
takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address),
|
||||
makerFee: new BigNumber(0),
|
||||
takerFee: new BigNumber(0),
|
||||
});
|
||||
const ordersWithoutFee = [orderWithoutFee];
|
||||
const feeOrders: SignedOrder[] = [];
|
||||
// The taker will receive more than the desired amount of makerAsset due to rounding
|
||||
const desiredMakerAssetFillAmount = new BigNumber('5000000000000000000');
|
||||
const ethValue = new BigNumber('4084971271824171');
|
||||
const makerAssetFillAmount = ethValue
|
||||
.times(orderWithoutFee.makerAssetAmount)
|
||||
.dividedToIntegerBy(orderWithoutFee.takerAssetAmount);
|
||||
// Execute test case
|
||||
tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(
|
||||
ordersWithoutFee,
|
||||
feeOrders,
|
||||
desiredMakerAssetFillAmount,
|
||||
{
|
||||
value: ethValue,
|
||||
from: takerAddress,
|
||||
},
|
||||
);
|
||||
// Fetch end balances and construct expected outputs
|
||||
const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
|
||||
const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
|
||||
const newBalances = await erc20Wrapper.getBalancesAsync();
|
||||
const primaryTakerAssetFillAmount = ethValue;
|
||||
const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
|
||||
// Validate test case
|
||||
expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount);
|
||||
expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
|
||||
expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
it('Should buy correct MakerAsset when exchange rate is NOT rounded, and MakerAsset is ZRX (Regression Test)', async () => {
|
||||
// An extra unit of TakerAsset was sent to the exchange contract to account for rounding errors, in Forwarder v1.
|
||||
// Specifically, the takerFillAmount was calculated using Floor(desiredMakerAmount * exchangeRate) + 1
|
||||
// We have since changed this to be Ceil(desiredMakerAmount * exchangeRate)
|
||||
// These calculations produce different results when `desiredMakerAmount * exchangeRate` is an integer.
|
||||
//
|
||||
// This test verifies that `ceil` is sufficient:
|
||||
// Let TakerAssetAmount = MakerAssetAmount * 2
|
||||
// -> exchangeRate = TakerAssetAmount / MakerAssetAmount = (2*MakerAssetAmount)/MakerAssetAmount = 2
|
||||
// .: desiredMakerAmount * exchangeRate is an integer.
|
||||
//
|
||||
// Construct test case using values from example above
|
||||
orderWithoutFee = await orderFactory.newSignedOrderAsync({
|
||||
makerAssetAmount: new BigNumber('30'),
|
||||
takerAssetAmount: new BigNumber('60'),
|
||||
makerAssetData: zrxAssetData,
|
||||
takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address),
|
||||
makerFee: new BigNumber(0),
|
||||
takerFee: new BigNumber(0),
|
||||
});
|
||||
const ordersWithoutFee = [orderWithoutFee];
|
||||
const feeOrders: SignedOrder[] = [];
|
||||
const makerAssetFillAmount = new BigNumber('5');
|
||||
const ethValue = new BigNumber('10');
|
||||
// Execute test case
|
||||
tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
|
||||
value: ethValue,
|
||||
from: takerAddress,
|
||||
});
|
||||
// Fetch end balances and construct expected outputs
|
||||
const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
|
||||
const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
|
||||
const newBalances = await erc20Wrapper.getBalancesAsync();
|
||||
const primaryTakerAssetFillAmount = ethValue;
|
||||
const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
|
||||
// Validate test case
|
||||
expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
|
||||
expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
});
|
||||
describe('marketBuyOrdersWithEth with extra fees', () => {
|
||||
it('should buy an asset and send fee to feeRecipient', async () => {
|
||||
|
||||
@@ -26,9 +26,12 @@ export class ForwarderWrapper {
|
||||
_.forEach(feeOrders, feeOrder => {
|
||||
const feeAvailable = feeOrder.makerAssetAmount.minus(feeOrder.takerFee);
|
||||
if (!remainingFeeAmount.isZero() && feeAvailable.gt(remainingFeeAmount)) {
|
||||
wethAmount = wethAmount
|
||||
.plus(feeOrder.takerAssetAmount.times(remainingFeeAmount).dividedToIntegerBy(feeAvailable))
|
||||
.plus(1);
|
||||
wethAmount = wethAmount.plus(
|
||||
feeOrder.takerAssetAmount
|
||||
.times(remainingFeeAmount)
|
||||
.dividedBy(feeAvailable)
|
||||
.ceil(),
|
||||
);
|
||||
remainingFeeAmount = new BigNumber(0);
|
||||
} else if (!remainingFeeAmount.isZero()) {
|
||||
wethAmount = wethAmount.plus(feeOrder.takerAssetAmount);
|
||||
|
||||
@@ -23,7 +23,7 @@ export class BuyButton extends React.Component<BuyButtonProps> {
|
||||
onBuyFailure: util.boundNoop,
|
||||
};
|
||||
public render(): React.ReactNode {
|
||||
const shouldDisableButton = _.isUndefined(this.props.buyQuote);
|
||||
const shouldDisableButton = _.isUndefined(this.props.buyQuote) || _.isUndefined(this.props.assetBuyer);
|
||||
return (
|
||||
<Button width="100%" onClick={this._handleClick} isDisabled={shouldDisableButton}>
|
||||
<Text fontColor={ColorOption.white} fontWeight={600} fontSize="20px">
|
||||
|
||||
@@ -31,7 +31,7 @@ export interface ZeroExInstantOptionalProps {
|
||||
}
|
||||
|
||||
export class ZeroExInstant extends React.Component<ZeroExInstantProps> {
|
||||
public store: Store;
|
||||
private readonly _store: Store;
|
||||
private static _mergeInitialStateWithProps(props: ZeroExInstantProps, state: State = INITIAL_STATE): State {
|
||||
// Create merged object such that properties in props override default settings
|
||||
const optionalPropsWithDefaults: ZeroExInstantOptionalProps = {
|
||||
@@ -58,14 +58,14 @@ export class ZeroExInstant extends React.Component<ZeroExInstantProps> {
|
||||
}
|
||||
constructor(props: ZeroExInstantProps) {
|
||||
super(props);
|
||||
this.store = store.create(ZeroExInstant._mergeInitialStateWithProps(this.props, INITIAL_STATE));
|
||||
this._store = store.create(ZeroExInstant._mergeInitialStateWithProps(this.props, INITIAL_STATE));
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
asyncData.fetchAndDispatchToStore(this.store);
|
||||
asyncData.fetchAndDispatchToStore(this._store);
|
||||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
<Provider store={this.store}>
|
||||
<Provider store={this._store}>
|
||||
<SelectedAssetThemeProvider>
|
||||
<ZeroExInstantContainer />
|
||||
</SelectedAssetThemeProvider>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
export const BIG_NUMBER_ZERO = new BigNumber(0);
|
||||
export const ethDecimals = 18;
|
||||
export const DEFAULT_ZERO_EX_CONTAINER_SELECTOR = '#zeroExInstantContainer';
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
|
||||
import { DEFAULT_ZERO_EX_CONTAINER_SELECTOR } from './constants';
|
||||
import { ZeroExInstant, ZeroExInstantProps } from './index';
|
||||
|
||||
export const render = (props: ZeroExInstantProps, selector: string = '#zeroExInstantContainer') => {
|
||||
export const render = (props: ZeroExInstantProps, selector: string = DEFAULT_ZERO_EX_CONTAINER_SELECTOR) => {
|
||||
ReactDOM.render(React.createElement(ZeroExInstant, props), document.querySelector(selector));
|
||||
};
|
||||
|
||||
@@ -41,6 +41,7 @@ export interface ERC721Asset {
|
||||
assetData: string;
|
||||
metaData: ERC721AssetMetaData;
|
||||
}
|
||||
|
||||
export interface Asset {
|
||||
assetData: string;
|
||||
metaData: AssetMetaData;
|
||||
|
||||
47
packages/instant/test/util/asset.test.ts
Normal file
47
packages/instant/test/util/asset.test.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { AssetProxyId, ObjectMap } from '@0x/types';
|
||||
|
||||
import { Asset, AssetMetaData, ERC20AssetMetaData, Network, ZeroExInstantError } from '../../src/types';
|
||||
import { assetUtils } from '../../src/util/asset';
|
||||
|
||||
const ZRX_ASSET_DATA = '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498';
|
||||
const ZRX_ASSET_DATA_KOVAN = '0xf47261b00000000000000000000000002002d3812f58e35f0ea1ffbf80a75a38c32175fa';
|
||||
const ZRX_META_DATA: ERC20AssetMetaData = {
|
||||
assetProxyId: AssetProxyId.ERC20,
|
||||
symbol: 'zrx',
|
||||
decimals: 18,
|
||||
};
|
||||
const ZRX_ASSET: Asset = {
|
||||
assetData: ZRX_ASSET_DATA,
|
||||
metaData: ZRX_META_DATA,
|
||||
};
|
||||
const META_DATA_MAP: ObjectMap<AssetMetaData> = {
|
||||
[ZRX_ASSET_DATA]: ZRX_META_DATA,
|
||||
};
|
||||
|
||||
describe('assetDataUtil', () => {
|
||||
describe('bestNameForAsset', () => {
|
||||
it('should return default string if assetData is undefined', () => {
|
||||
expect(assetUtils.bestNameForAsset(undefined, 'xyz')).toEqual('xyz');
|
||||
});
|
||||
it('should return ZRX for ZRX assetData', () => {
|
||||
expect(assetUtils.bestNameForAsset(ZRX_ASSET, 'mah default')).toEqual('ZRX');
|
||||
});
|
||||
});
|
||||
describe('getMetaDataOrThrow', () => {
|
||||
it('should return the metaData for the supplied mainnet asset data', () => {
|
||||
expect(assetUtils.getMetaDataOrThrow(ZRX_ASSET_DATA, META_DATA_MAP, Network.Mainnet)).toEqual(
|
||||
ZRX_META_DATA,
|
||||
);
|
||||
});
|
||||
it('should return the metaData for the supplied non-mainnet asset data', () => {
|
||||
expect(assetUtils.getMetaDataOrThrow(ZRX_ASSET_DATA_KOVAN, META_DATA_MAP, Network.Kovan)).toEqual(
|
||||
ZRX_META_DATA,
|
||||
);
|
||||
});
|
||||
it('should throw if the metaData for the asset is not available', () => {
|
||||
expect(() =>
|
||||
assetUtils.getMetaDataOrThrow('asset data we dont have', META_DATA_MAP, Network.Mainnet),
|
||||
).toThrowError(ZeroExInstantError.AssetMetaDataNotAvailable);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,14 @@
|
||||
[
|
||||
{
|
||||
"version": "2.0.1",
|
||||
"changes": [
|
||||
{
|
||||
"note":
|
||||
"Improve schemas by enforcing that amounts that must be whole numbers (e.g Order asset amounts) no longer allow decimal amounts",
|
||||
"pr": 1173
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"changes": [
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"items": {
|
||||
"properties": {
|
||||
"order": { "$ref": "/orderSchema" },
|
||||
"takerTokenCancelAmount": { "$ref": "/numberSchema" }
|
||||
"takerTokenCancelAmount": { "$ref": "/wholeNumberSchema" }
|
||||
},
|
||||
"required": ["order", "takerTokenCancelAmount"],
|
||||
"type": "object"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"items": {
|
||||
"properties": {
|
||||
"signedOrder": { "$ref": "/signedOrderSchema" },
|
||||
"fillTakerAmount": { "$ref": "/numberSchema" }
|
||||
"fillTakerAmount": { "$ref": "/wholeNumberSchema" }
|
||||
},
|
||||
"required": ["signedOrder", "fillTakerAmount"],
|
||||
"type": "object"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"items": {
|
||||
"properties": {
|
||||
"signedOrder": { "$ref": "/signedOrderSchema" },
|
||||
"takerTokenFillAmount": { "$ref": "/numberSchema" }
|
||||
"takerTokenFillAmount": { "$ref": "/wholeNumberSchema" }
|
||||
},
|
||||
"required": ["signedOrder", "takerTokenFillAmount"],
|
||||
"type": "object"
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
"properties": {
|
||||
"makerAddress": { "$ref": "/addressSchema" },
|
||||
"takerAddress": { "$ref": "/addressSchema" },
|
||||
"makerFee": { "$ref": "/numberSchema" },
|
||||
"takerFee": { "$ref": "/numberSchema" },
|
||||
"makerFee": { "$ref": "/wholeNumberSchema" },
|
||||
"takerFee": { "$ref": "/wholeNumberSchema" },
|
||||
"senderAddress": { "$ref": "/addressSchema" },
|
||||
"makerAssetAmount": { "$ref": "/numberSchema" },
|
||||
"takerAssetAmount": { "$ref": "/numberSchema" },
|
||||
"makerAssetAmount": { "$ref": "/wholeNumberSchema" },
|
||||
"takerAssetAmount": { "$ref": "/wholeNumberSchema" },
|
||||
"makerAssetData": { "$ref": "/hexSchema" },
|
||||
"takerAssetData": { "$ref": "/hexSchema" },
|
||||
"salt": { "$ref": "/numberSchema" },
|
||||
"salt": { "$ref": "/wholeNumberSchema" },
|
||||
"exchangeAddress": { "$ref": "/addressSchema" },
|
||||
"feeRecipientAddress": { "$ref": "/addressSchema" },
|
||||
"expirationTimeSeconds": { "$ref": "/numberSchema" }
|
||||
"expirationTimeSeconds": { "$ref": "/wholeNumberSchema" }
|
||||
},
|
||||
"required": [
|
||||
"makerAddress",
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"assetData": { "$ref": "/hexSchema" },
|
||||
"minAmount": { "$ref": "/numberSchema" },
|
||||
"maxAmount": { "$ref": "/numberSchema" },
|
||||
"minAmount": { "$ref": "/wholeNumberSchema" },
|
||||
"maxAmount": { "$ref": "/wholeNumberSchema" },
|
||||
"precision": { "type": "number" }
|
||||
},
|
||||
"required": ["assetData"]
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
"properties": {
|
||||
"makerAddress": { "$ref": "/addressSchema" },
|
||||
"takerAddress": { "$ref": "/addressSchema" },
|
||||
"makerAssetAmount": { "$ref": "/numberSchema" },
|
||||
"takerAssetAmount": { "$ref": "/numberSchema" },
|
||||
"makerAssetAmount": { "$ref": "/wholeNumberSchema" },
|
||||
"takerAssetAmount": { "$ref": "/wholeNumberSchema" },
|
||||
"makerAssetData": { "$ref": "/hexSchema" },
|
||||
"takerAssetData": { "$ref": "/hexSchema" },
|
||||
"exchangeAddress": { "$ref": "/addressSchema" },
|
||||
"expirationTimeSeconds": { "$ref": "/numberSchema" }
|
||||
"expirationTimeSeconds": { "$ref": "/wholeNumberSchema" }
|
||||
},
|
||||
"required": [
|
||||
"makerAddress",
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"id": "/relayerApiOrderConfigResponseSchema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"makerFee": { "$ref": "/numberSchema" },
|
||||
"takerFee": { "$ref": "/numberSchema" },
|
||||
"makerFee": { "$ref": "/wholeNumberSchema" },
|
||||
"takerFee": { "$ref": "/wholeNumberSchema" },
|
||||
"feeRecipientAddress": { "$ref": "/addressSchema" },
|
||||
"senderAddress": { "$ref": "/addressSchema" }
|
||||
},
|
||||
|
||||
5
packages/json-schemas/schemas/whole_number_schema.json
Normal file
5
packages/json-schemas/schemas/whole_number_schema.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"id": "/wholeNumberSchema",
|
||||
"type": "string",
|
||||
"pattern": "^\\d+$"
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
"properties": {
|
||||
"data": { "$ref": "/hexSchema" },
|
||||
"signerAddress": { "$ref": "/addressSchema" },
|
||||
"salt": { "$ref": "/numberSchema" }
|
||||
"salt": { "$ref": "/wholeNumberSchema" }
|
||||
},
|
||||
"required": ["data", "salt", "signerAddress"],
|
||||
"type": "object"
|
||||
|
||||
@@ -34,6 +34,7 @@ import * as signedOrderSchema from '../schemas/signed_order_schema.json';
|
||||
import * as signedOrdersSchema from '../schemas/signed_orders_schema.json';
|
||||
import * as tokenSchema from '../schemas/token_schema.json';
|
||||
import * as txDataSchema from '../schemas/tx_data_schema.json';
|
||||
import * as wholeNumberSchema from '../schemas/whole_number_schema.json';
|
||||
import * as zeroExTransactionSchema from '../schemas/zero_ex_transaction_schema.json';
|
||||
|
||||
export const schemas = {
|
||||
@@ -74,4 +75,5 @@ export const schemas = {
|
||||
relayerApiOrdersResponseSchema,
|
||||
relayerApiAssetDataPairsSchema,
|
||||
zeroExTransactionSchema,
|
||||
wholeNumberSchema,
|
||||
};
|
||||
|
||||
@@ -36,6 +36,7 @@ const {
|
||||
relayerApiOrdersChannelUpdateSchema,
|
||||
relayerApiOrdersResponseSchema,
|
||||
relayerApiOrderSchema,
|
||||
wholeNumberSchema,
|
||||
} = schemas;
|
||||
|
||||
describe('Schema', () => {
|
||||
@@ -73,6 +74,17 @@ describe('Schema', () => {
|
||||
validateAgainstSchema(testCases, numberSchema, shouldFail);
|
||||
});
|
||||
});
|
||||
describe('#wholeNumberSchema', () => {
|
||||
it('should validate valid numbers', () => {
|
||||
const testCases = ['5', '42', '0'];
|
||||
validateAgainstSchema(testCases, wholeNumberSchema);
|
||||
});
|
||||
it('should fail for invalid numbers', () => {
|
||||
const testCases = ['1.3', '0.2', '00.00', '.3', '1.', 'abacaba', 'и', '1..0'];
|
||||
const shouldFail = true;
|
||||
validateAgainstSchema(testCases, wholeNumberSchema, shouldFail);
|
||||
});
|
||||
});
|
||||
describe('#addressSchema', () => {
|
||||
it('should validate valid addresses', () => {
|
||||
const testCases = ['0x8b0292b11a196601ed2ce54b665cafeca0347d42', NULL_ADDRESS];
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
"./schemas/js_number.json",
|
||||
"./schemas/zero_ex_transaction_schema.json",
|
||||
"./schemas/tx_data_schema.json",
|
||||
"./schemas/index_filter_values_schema.json"
|
||||
"./schemas/index_filter_values_schema.json",
|
||||
"./schemas/whole_number_schema.json"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
{
|
||||
"note": "Add AssetBuyerError to the IGNORED_EXCESSIVE_TYPES array",
|
||||
"pr": 1139
|
||||
},
|
||||
{
|
||||
"note": "Add ForwarderError to the IGNORED_EXCESSIVE_TYPES array",
|
||||
"pr": 1147
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -56,6 +56,7 @@ export const docGenConfigs: DocGenConfigs = {
|
||||
'ContractWrappersError',
|
||||
'OrderError',
|
||||
'AssetBuyerError',
|
||||
'ForwarderWrapperError',
|
||||
],
|
||||
// Some libraries only export types. In those cases, we cannot check if the exported types are part of the
|
||||
// "exported public interface". Thus we add them here and skip those checks.
|
||||
|
||||
31
packages/order-utils/test/asset_data_utils_test.ts
Normal file
31
packages/order-utils/test/asset_data_utils_test.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import * as chai from 'chai';
|
||||
|
||||
import { ERC20AssetData } from '@0x/types';
|
||||
|
||||
import { assetDataUtils } from '../src/asset_data_utils';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
const KNOWN_ENCODINGS = [
|
||||
{
|
||||
address: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48',
|
||||
assetData: '0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48',
|
||||
},
|
||||
];
|
||||
|
||||
const ERC20_ASSET_PROXY_ID = '0xf47261b0';
|
||||
|
||||
describe('assetDataUtils', () => {
|
||||
it('should encode', () => {
|
||||
const assetData = assetDataUtils.encodeERC20AssetData(KNOWN_ENCODINGS[0].address);
|
||||
expect(assetData).to.equal(KNOWN_ENCODINGS[0].assetData);
|
||||
});
|
||||
it('should decode', () => {
|
||||
const assetData: ERC20AssetData = assetDataUtils.decodeERC20AssetData(KNOWN_ENCODINGS[0].assetData);
|
||||
expect(assetData.tokenAddress).to.equal(KNOWN_ENCODINGS[0].address);
|
||||
expect(assetData.assetProxyId).to.equal(ERC20_ASSET_PROXY_ID);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,14 @@
|
||||
[
|
||||
{
|
||||
"version": "3.1.1",
|
||||
"changes": [
|
||||
{
|
||||
"note":
|
||||
"Fix bug in `getTransactionByHashAsync` which was causing the return value to have the wrong type (raw fields instead of unmarshalled fields).",
|
||||
"pr": 1177
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "3.1.0",
|
||||
"changes": [
|
||||
|
||||
@@ -23,7 +23,13 @@ import {
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { marshaller } from './marshaller';
|
||||
import { BlockWithoutTransactionDataRPC, BlockWithTransactionDataRPC, NodeType, Web3WrapperErrors } from './types';
|
||||
import {
|
||||
BlockWithoutTransactionDataRPC,
|
||||
BlockWithTransactionDataRPC,
|
||||
NodeType,
|
||||
TransactionRPC,
|
||||
Web3WrapperErrors,
|
||||
} from './types';
|
||||
import { utils } from './utils';
|
||||
|
||||
const BASE_TEN = 10;
|
||||
@@ -228,10 +234,11 @@ export class Web3Wrapper {
|
||||
*/
|
||||
public async getTransactionByHashAsync(txHash: string): Promise<Transaction> {
|
||||
assert.isHexString('txHash', txHash);
|
||||
const transaction = await this.sendRawPayloadAsync<Transaction>({
|
||||
const transactionRpc = await this.sendRawPayloadAsync<TransactionRPC>({
|
||||
method: 'eth_getTransactionByHash',
|
||||
params: [txHash],
|
||||
});
|
||||
const transaction = marshaller.unmarshalTransaction(transactionRpc);
|
||||
return transaction;
|
||||
}
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
**Install**
|
||||
#### Install
|
||||
|
||||
```bash
|
||||
yarn add @0x/asset-buyer
|
||||
```
|
||||
|
||||
**Import**
|
||||
#### Import
|
||||
|
||||
```javascript
|
||||
import { AssetBuyer } from '@0x/asset-buyer';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
**Construction**
|
||||
#### Construction
|
||||
|
||||
Connecting to a standard relayer API compliant url:
|
||||
|
||||
@@ -16,7 +16,7 @@ const orders = []; // get these from your own API, a relayer, a friend, from any
|
||||
const assetBuyer = AssetBuyer.getAssetBuyerForProvidedOrders(provider, orders);
|
||||
```
|
||||
|
||||
**Get a quote**
|
||||
#### Get a quote
|
||||
|
||||
A [BuyQuote](#types-BuyQuote) object contains enough information to display buy information to an end user
|
||||
|
||||
@@ -30,7 +30,7 @@ console.log(quoteInfo.feeAmount); // a portion of the total ethAmount above that
|
||||
console.log(quoteInfo.ethPerAssetPrice); // the rate that this quote provides (e.g. 0.0035ETH / REP)
|
||||
```
|
||||
|
||||
**Perform a buy**
|
||||
#### Perform a buy
|
||||
|
||||
Pass the [BuyQuote](#types-BuyQuote) object from above back to the assetBuyer in order to initiate the buy transaction
|
||||
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
Basic Schemas
|
||||
|
||||
* [Address type](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/address.json)
|
||||
* [Number type](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/number.json)
|
||||
* [Hex type](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/hex.json)
|
||||
* [JS number](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/js_number.json)
|
||||
* [Address type](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/address_schema.json)
|
||||
* [Number type](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/number_schema.json)
|
||||
* [Hex type](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/hex_schema.json)
|
||||
* [JS number](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/js_number.json)
|
||||
|
||||
0x Protocol Schemas
|
||||
|
||||
* [Order](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/order_schema.json)
|
||||
* [SignedOrder](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/signed_order_schema.json)
|
||||
* [OrderHash](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/order_hash_schema.json)
|
||||
* [ECSignature](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/ec_signature_schema.json)
|
||||
* [Order](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/order_schema.json)
|
||||
* [SignedOrder](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/signed_order_schema.json)
|
||||
* [OrderHash](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/order_hash_schema.json)
|
||||
* [ECSignature](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/ec_signature_schema.json)
|
||||
|
||||
0x.js Schemas
|
||||
|
||||
* [BlockParam](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/block_param_schema.json)
|
||||
* [BlockRange](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/block_range_schema.json)
|
||||
* [IndexFilter Values](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/index_filter_values_schema.json)
|
||||
* [OrderFillRequests](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/order_fill_requests_schema.json)
|
||||
* [OrderCancellationRequests](https://github.com/0xProjet/0x-monorepo/blob/development/packages/json-schemas/schemas/order_cancel_schema.json)
|
||||
* [OrderFillOrKillRequests](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/order_fill_or_kill_requests_schema.json)
|
||||
* [SignedOrders](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/signed_orders_schema.json)
|
||||
* [Token](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/token_schema.json)
|
||||
* [TxData](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/tx_data_schema.json)
|
||||
* [BlockParam](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/block_param_schema.json)
|
||||
* [BlockRange](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/block_range_schema.json)
|
||||
* [IndexFilter Values](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/index_filter_values_schema.json)
|
||||
* [OrderFillRequests](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/order_fill_requests_schema.json)
|
||||
* [OrderCancellationRequests](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/order_cancel_schema.json)
|
||||
* [OrderFillOrKillRequests](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/order_fill_or_kill_requests_schema.json)
|
||||
* [SignedOrders](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/signed_orders_schema.json)
|
||||
* [Token](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/token_schema.json)
|
||||
* [TxData](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/tx_data_schema.json)
|
||||
|
||||
Standard Relayer API Schemas
|
||||
|
||||
* [Paginated collection](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/paginated_collection_schema.json)
|
||||
* [Error response](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_error_response_schema.json)
|
||||
* [Order config payload](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_order_config_payload_schema.json)
|
||||
* [Order config response](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_order_config_response_schema.json)
|
||||
* [Orders channel subscribe payload](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_orders_channel_subscribe_payload_schema.json)
|
||||
* [Orders channel subscribe](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_orders_channel_subscribe_schema.json)
|
||||
* [Orders channel update](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_orders_channel_update_response_schema.json)
|
||||
* [Orderbook response](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_orderbook_response_schema.json)
|
||||
* [Asset pairs](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_asset_pairs_schema.json)
|
||||
* [Trade info](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_asset_trade_info_schema.json)
|
||||
* [Asset pairs response](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_asset_pairs_response_schema.json)
|
||||
* [Fee recipients response](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_fee_recipients_response_schema.json)
|
||||
* [Paginated collection](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/paginated_collection_schema.json)
|
||||
* [Error response](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_error_response_schema.json)
|
||||
* [Order config payload](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_order_config_payload_schema.json)
|
||||
* [Order config response](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_order_config_response_schema.json)
|
||||
* [Orders channel subscribe payload](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_orders_channel_subscribe_payload_schema.json)
|
||||
* [Orders channel subscribe](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_orders_channel_subscribe_schema.json)
|
||||
* [Orders channel update](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_orders_channel_update_response_schema.json)
|
||||
* [Orderbook response](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_orderbook_response_schema.json)
|
||||
* [Asset pairs](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_asset_data_pairs_schema.json)
|
||||
* [Trade info](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_asset_data_trade_info_schema.json)
|
||||
* [Asset pairs response](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_asset_data_pairs_response_schema.json)
|
||||
* [Fee recipients response](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_fee_recipients_response_schema.json)
|
||||
|
||||
41
packages/website/md/docs/json_schemas/3/schemas.md
Normal file
41
packages/website/md/docs/json_schemas/3/schemas.md
Normal file
@@ -0,0 +1,41 @@
|
||||
Basic Schemas
|
||||
|
||||
* [Address type](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/address_schema.json)
|
||||
* [Number type](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/number_schema.json)
|
||||
* [Whole number type](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/whole_number_schema.json)
|
||||
* [Hex type](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/hex_schema.json)
|
||||
* [JS number](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/js_number.json)
|
||||
|
||||
0x Protocol Schemas
|
||||
|
||||
* [Order](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/order_schema.json)
|
||||
* [SignedOrder](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/signed_order_schema.json)
|
||||
* [OrderHash](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/order_hash_schema.json)
|
||||
* [ECSignature](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/ec_signature_schema.json)
|
||||
|
||||
0x.js Schemas
|
||||
|
||||
* [BlockParam](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/block_param_schema.json)
|
||||
* [BlockRange](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/block_range_schema.json)
|
||||
* [IndexFilter Values](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/index_filter_values_schema.json)
|
||||
* [OrderFillRequests](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/order_fill_requests_schema.json)
|
||||
* [OrderCancellationRequests](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/order_cancel_schema.json)
|
||||
* [OrderFillOrKillRequests](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/order_fill_or_kill_requests_schema.json)
|
||||
* [SignedOrders](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/signed_orders_schema.json)
|
||||
* [Token](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/token_schema.json)
|
||||
* [TxData](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/tx_data_schema.json)
|
||||
|
||||
Standard Relayer API Schemas
|
||||
|
||||
* [Paginated collection](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/paginated_collection_schema.json)
|
||||
* [Error response](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_error_response_schema.json)
|
||||
* [Order config payload](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_order_config_payload_schema.json)
|
||||
* [Order config response](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_order_config_response_schema.json)
|
||||
* [Orders channel subscribe payload](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_orders_channel_subscribe_payload_schema.json)
|
||||
* [Orders channel subscribe](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_orders_channel_subscribe_schema.json)
|
||||
* [Orders channel update](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_orders_channel_update_response_schema.json)
|
||||
* [Orderbook response](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_orderbook_response_schema.json)
|
||||
* [Asset pairs](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_asset_data_pairs_schema.json)
|
||||
* [Trade info](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_asset_data_trade_info_schema.json)
|
||||
* [Asset pairs response](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_asset_data_pairs_response_schema.json)
|
||||
* [Fee recipients response](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_fee_recipients_response_schema.json)
|
||||
@@ -163,7 +163,7 @@ export class Blockchain {
|
||||
const providerName = this._getNameGivenProvider(injectedWeb3.currentProvider);
|
||||
// Wrap Metamask in a compatability wrapper MetamaskSubprovider (to handle inconsistencies)
|
||||
const signerSubprovider =
|
||||
providerName === Providers.Metamask
|
||||
providerName === constants.PROVIDER_NAME_METAMASK
|
||||
? new MetamaskSubprovider(injectedWeb3.currentProvider)
|
||||
: new SignerSubprovider(injectedWeb3.currentProvider);
|
||||
provider.addProvider(signerSubprovider);
|
||||
|
||||
@@ -210,12 +210,16 @@ export class Portal extends React.Component<PortalProps, PortalState> {
|
||||
isLoaded: false,
|
||||
};
|
||||
}
|
||||
this.setState({
|
||||
trackedTokenStateByAddress,
|
||||
});
|
||||
// Fetch the actual balance/allowance.
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
this._fetchBalancesAndAllowancesAsync(newTokenAddresses);
|
||||
this.setState(
|
||||
{
|
||||
trackedTokenStateByAddress,
|
||||
},
|
||||
() => {
|
||||
// Fetch the actual balance/allowance.
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
this._fetchBalancesAndAllowancesAsync(newTokenAddresses);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
public render(): React.ReactNode {
|
||||
@@ -644,6 +648,9 @@ export class Portal extends React.Component<PortalProps, PortalState> {
|
||||
}
|
||||
|
||||
private async _fetchBalancesAndAllowancesAsync(tokenAddresses: string[]): Promise<void> {
|
||||
if (_.isEmpty(tokenAddresses)) {
|
||||
return;
|
||||
}
|
||||
const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress;
|
||||
const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress;
|
||||
const balancesAndAllowances = await Promise.all(
|
||||
|
||||
@@ -15,8 +15,9 @@ const InstallationMarkdown1 = require('md/docs/json_schemas/1/installation');
|
||||
const InstallationMarkdown3 = require('md/docs/json_schemas/3/installation');
|
||||
const usageMarkdown1 = require('md/docs/json_schemas/1/usage');
|
||||
const usageMarkdown3 = require('md/docs/json_schemas/3/usage');
|
||||
const SchemasMarkdownV1 = require('md/docs/json_schemas/1/schemas');
|
||||
const SchemasMarkdownV2 = require('md/docs/json_schemas/2/schemas');
|
||||
const SchemasMarkdown1 = require('md/docs/json_schemas/1/schemas');
|
||||
const SchemasMarkdown2 = require('md/docs/json_schemas/2/schemas');
|
||||
const SchemasMarkdown3 = require('md/docs/json_schemas/3/schemas');
|
||||
/* tslint:enable:no-var-requires */
|
||||
|
||||
const markdownSections = {
|
||||
@@ -40,19 +41,25 @@ const docsInfoConfig: DocsInfoConfig = {
|
||||
'0.0.1': {
|
||||
[markdownSections.introduction]: IntroMarkdown1,
|
||||
[markdownSections.installation]: InstallationMarkdown1,
|
||||
[markdownSections.schemas]: SchemasMarkdownV1,
|
||||
[markdownSections.schemas]: SchemasMarkdown1,
|
||||
[markdownSections.usage]: usageMarkdown1,
|
||||
},
|
||||
'1.0.0': {
|
||||
[markdownSections.introduction]: IntroMarkdown1,
|
||||
[markdownSections.installation]: InstallationMarkdown1,
|
||||
[markdownSections.schemas]: SchemasMarkdownV2,
|
||||
[markdownSections.schemas]: SchemasMarkdown2,
|
||||
[markdownSections.usage]: usageMarkdown1,
|
||||
},
|
||||
'2.0.0': {
|
||||
[markdownSections.introduction]: IntroMarkdown3,
|
||||
[markdownSections.installation]: InstallationMarkdown3,
|
||||
[markdownSections.schemas]: SchemasMarkdownV2,
|
||||
[markdownSections.schemas]: SchemasMarkdown2,
|
||||
[markdownSections.usage]: usageMarkdown3,
|
||||
},
|
||||
'2.0.1': {
|
||||
[markdownSections.introduction]: IntroMarkdown3,
|
||||
[markdownSections.installation]: InstallationMarkdown3,
|
||||
[markdownSections.schemas]: SchemasMarkdown3,
|
||||
[markdownSections.usage]: usageMarkdown3,
|
||||
},
|
||||
},
|
||||
|
||||
39
python-packages/order_utils/setup.py
Normal file → Executable file
39
python-packages/order_utils/setup.py
Normal file → Executable file
@@ -1,13 +1,16 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""setuptools module for order_utils package."""
|
||||
|
||||
import subprocess # nosec
|
||||
from shutil import rmtree
|
||||
from os import path, remove, walk
|
||||
from os import environ, path, remove, walk
|
||||
from sys import argv
|
||||
|
||||
from distutils.command.clean import clean # type: ignore
|
||||
from setuptools import setup # type: ignore
|
||||
import setuptools.command.build_py # type: ignore
|
||||
from setuptools.command.test import test as TestCommand # type: ignore
|
||||
from distutils.command.clean import clean
|
||||
import distutils.command.build_py
|
||||
from setuptools import setup
|
||||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
|
||||
class TestCommandExtension(TestCommand):
|
||||
@@ -15,13 +18,13 @@ class TestCommandExtension(TestCommand):
|
||||
|
||||
def run_tests(self):
|
||||
"""Invoke pytest."""
|
||||
import pytest # type: ignore
|
||||
import pytest
|
||||
|
||||
pytest.main()
|
||||
|
||||
|
||||
# pylint: disable=too-many-ancestors
|
||||
class LintCommand(setuptools.command.build_py.build_py):
|
||||
class LintCommand(distutils.command.build_py.build_py):
|
||||
"""Custom setuptools command class for running linters."""
|
||||
|
||||
def run(self):
|
||||
@@ -34,7 +37,7 @@ class LintCommand(setuptools.command.build_py.build_py):
|
||||
# docstring style checker:
|
||||
"pydocstyle src test setup.py".split(),
|
||||
# static type checker:
|
||||
"mypy src setup.py".split(),
|
||||
"mypy src test setup.py".split(),
|
||||
# security issue checker:
|
||||
"bandit -r src ./setup.py".split(),
|
||||
# general linter:
|
||||
@@ -42,6 +45,21 @@ class LintCommand(setuptools.command.build_py.build_py):
|
||||
# pylint takes relatively long to run, so it runs last, to enable
|
||||
# fast failures.
|
||||
]
|
||||
|
||||
# tell mypy where to find interface stubs for 3rd party libs
|
||||
environ["MYPYPATH"] = path.join(
|
||||
path.dirname(path.realpath(argv[0])), "stubs"
|
||||
)
|
||||
|
||||
# HACK(gene): until eth_abi releases
|
||||
# https://github.com/ethereum/eth-abi/pull/107 , we need to simply
|
||||
# create an empty file `py.typed` in the eth_abi package directory.
|
||||
import eth_abi
|
||||
|
||||
eth_abi_dir = path.dirname(path.realpath(eth_abi.__file__))
|
||||
with open(path.join(eth_abi_dir, "py.typed"), "a"):
|
||||
pass
|
||||
|
||||
for lint_command in lint_commands:
|
||||
print(
|
||||
"Running lint command `", " ".join(lint_command).strip(), "`"
|
||||
@@ -79,7 +97,7 @@ setup(
|
||||
"test": TestCommandExtension,
|
||||
},
|
||||
include_package_data=True,
|
||||
install_requires=["web3"],
|
||||
install_requires=["eth-abi", "web3"],
|
||||
extras_require={
|
||||
"dev": [
|
||||
"bandit",
|
||||
@@ -87,6 +105,7 @@ setup(
|
||||
"coverage",
|
||||
"coveralls",
|
||||
"mypy",
|
||||
"mypy_extensions",
|
||||
"pycodestyle",
|
||||
"pydocstyle",
|
||||
"pylint",
|
||||
@@ -118,7 +137,7 @@ setup(
|
||||
"Topic :: Software Development :: Libraries",
|
||||
"Topic :: Utilities",
|
||||
],
|
||||
zip_safe=False,
|
||||
zip_safe=False, # required per mypy
|
||||
command_options={
|
||||
"build_sphinx": {
|
||||
"source_dir": ("setup.py", "src"),
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
# Reference: http://www.sphinx-doc.org/en/master/config
|
||||
|
||||
from typing import List
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
# because these variables are not named in upper case, as globals should be.
|
||||
|
||||
@@ -29,7 +32,7 @@ master_doc = "index" # The master toctree document.
|
||||
|
||||
language = None
|
||||
|
||||
exclude_patterns = [] # type: ignore
|
||||
exclude_patterns: List[str] = []
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = None
|
||||
|
||||
@@ -10,9 +10,12 @@ order_utils.py
|
||||
.. automodule:: zero_ex.order_utils
|
||||
:members:
|
||||
|
||||
.. automodule:: zero_ex.order_utils.signature_utils
|
||||
.. automodule:: zero_ex.order_utils.asset_data_utils
|
||||
:members:
|
||||
|
||||
.. autoclass:: zero_ex.order_utils.asset_data_utils.ERC20AssetData
|
||||
|
||||
See source for properties. Sphinx does not easily generate class property docs; pull requests welcome.
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
"""Dev utils to be shared across 0x projects and packages."""
|
||||
102
python-packages/order_utils/src/zero_ex/dev_utils/abi_utils.py
Normal file
102
python-packages/order_utils/src/zero_ex/dev_utils/abi_utils.py
Normal file
@@ -0,0 +1,102 @@
|
||||
"""Ethereum ABI utilities.
|
||||
|
||||
Builds on the eth-abi package, adding some convenience methods like those found
|
||||
in npmjs.com/package/ethereumjs-abi. Ideally, all of this code should be
|
||||
pushed upstream into eth-abi.
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Any, List
|
||||
|
||||
from mypy_extensions import TypedDict
|
||||
|
||||
from eth_abi import encode_abi
|
||||
from web3 import Web3
|
||||
|
||||
from .type_assertions import assert_is_string, assert_is_list
|
||||
|
||||
|
||||
class MethodSignature(TypedDict, total=False):
|
||||
"""Object interface to an ABI method signature."""
|
||||
|
||||
method: str
|
||||
args: List[str]
|
||||
|
||||
|
||||
def parse_signature(signature: str) -> MethodSignature:
|
||||
"""Parse a method signature into its constituent parts.
|
||||
|
||||
>>> parse_signature("ERC20Token(address)")
|
||||
{'method': 'ERC20Token', 'args': ['address']}
|
||||
"""
|
||||
assert_is_string(signature, "signature")
|
||||
|
||||
matches = re.match(r"^(\w+)\((.+)\)$", signature)
|
||||
if matches is None:
|
||||
raise ValueError(f"Invalid method signature {signature}")
|
||||
return {"method": matches[1], "args": matches[2].split(",")}
|
||||
|
||||
|
||||
def elementary_name(name: str) -> str:
|
||||
"""Convert from short to canonical names; barely implemented.
|
||||
|
||||
Modeled after ethereumjs-abi's ABI.elementaryName(), but only implemented
|
||||
to support our particular use case and a few other simple ones.
|
||||
|
||||
>>> elementary_name("address")
|
||||
'address'
|
||||
>>> elementary_name("uint")
|
||||
'uint256'
|
||||
"""
|
||||
assert_is_string(name, "name")
|
||||
|
||||
return {
|
||||
"int": "int256",
|
||||
"uint": "uint256",
|
||||
"fixed": "fixed128x128",
|
||||
"ufixed": "ufixed128x128",
|
||||
}.get(name, name)
|
||||
|
||||
|
||||
def event_id(name: str, types: List[str]) -> str:
|
||||
"""Return the Keccak-256 hash of the given method.
|
||||
|
||||
>>> event_id("ERC20Token", ["address"])
|
||||
'0xf47261b06eedbfce68afd46d0f3c27c60b03faad319eaf33103611cf8f6456ad'
|
||||
"""
|
||||
assert_is_string(name, "name")
|
||||
assert_is_list(types, "types")
|
||||
|
||||
signature = f"{name}({','.join(list(map(elementary_name, types)))})"
|
||||
return Web3.sha3(text=signature).hex()
|
||||
|
||||
|
||||
def method_id(name: str, types: List[str]) -> str:
|
||||
"""Return the 4-byte method identifier.
|
||||
|
||||
>>> method_id("ERC20Token", ["address"])
|
||||
'0xf47261b0'
|
||||
"""
|
||||
assert_is_string(name, "name")
|
||||
assert_is_list(types, "types")
|
||||
|
||||
return event_id(name, types)[0:10]
|
||||
|
||||
|
||||
def simple_encode(method: str, *args: Any) -> bytes:
|
||||
# docstring considered all one line by pylint: disable=line-too-long
|
||||
r"""Encode a method ABI.
|
||||
|
||||
>>> simple_encode("ERC20Token(address)", "0x1dc4c1cefef38a777b15aa20260a54e584b16c48")
|
||||
b'\xf4ra\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1d\xc4\xc1\xce\xfe\xf3\x8aw{\x15\xaa &\nT\xe5\x84\xb1lH'
|
||||
""" # noqa: E501 (line too long)
|
||||
assert_is_string(method, "method")
|
||||
|
||||
signature: MethodSignature = parse_signature(method)
|
||||
|
||||
return bytes.fromhex(
|
||||
(
|
||||
method_id(signature["method"], signature["args"])
|
||||
+ encode_abi(signature["args"], args).hex()
|
||||
)[2:]
|
||||
)
|
||||
@@ -0,0 +1,33 @@
|
||||
"""Assertions for runtime type checking of function arguments."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def assert_is_string(value: Any, name: str) -> None:
|
||||
"""If :param value: isn't of type str, raise a TypeError.
|
||||
|
||||
>>> try: assert_is_string(123, 'var')
|
||||
... except TypeError as type_error: print(str(type_error))
|
||||
...
|
||||
expected variable 'var', with value 123, to have type 'str', not 'int'
|
||||
"""
|
||||
if not isinstance(value, str):
|
||||
raise TypeError(
|
||||
f"expected variable '{name}', with value {str(value)}, to have"
|
||||
+ f" type 'str', not '{type(value).__name__}'"
|
||||
)
|
||||
|
||||
|
||||
def assert_is_list(value: Any, name: str) -> None:
|
||||
"""If :param value: isn't of type list, raise a TypeError.
|
||||
|
||||
>>> try: assert_is_list(123, 'var')
|
||||
... except TypeError as type_error: print(str(type_error))
|
||||
...
|
||||
expected variable 'var', with value 123, to have type 'list', not 'int'
|
||||
"""
|
||||
if not isinstance(value, list):
|
||||
raise TypeError(
|
||||
f"expected variable '{name}', with value {str(value)}, to have"
|
||||
+ f" type 'list', not '{type(value).__name__}'"
|
||||
)
|
||||
@@ -0,0 +1,72 @@
|
||||
"""Asset data encoding and decoding utilities."""
|
||||
|
||||
from mypy_extensions import TypedDict
|
||||
|
||||
import eth_abi
|
||||
|
||||
from zero_ex.dev_utils import abi_utils
|
||||
from zero_ex.dev_utils.type_assertions import assert_is_string
|
||||
|
||||
|
||||
ERC20_ASSET_DATA_BYTE_LENGTH = 36
|
||||
SELECTOR_LENGTH = 10
|
||||
|
||||
|
||||
class ERC20AssetData(TypedDict):
|
||||
"""Object interface to ERC20 asset data."""
|
||||
|
||||
asset_proxy_id: str
|
||||
token_address: str
|
||||
|
||||
|
||||
def encode_erc20_asset_data(token_address: str) -> str:
|
||||
"""Encode an ERC20 token address into an asset data string.
|
||||
|
||||
:param token_address: the ERC20 token's contract address.
|
||||
:rtype: hex encoded asset data string, usable in the makerAssetData or
|
||||
takerAssetData fields in a 0x order.
|
||||
|
||||
>>> encode_erc20_asset_data('0x1dc4c1cefef38a777b15aa20260a54e584b16c48')
|
||||
'0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48'
|
||||
"""
|
||||
assert_is_string(token_address, "token_address")
|
||||
|
||||
return (
|
||||
"0x"
|
||||
+ abi_utils.simple_encode("ERC20Token(address)", token_address).hex()
|
||||
)
|
||||
|
||||
|
||||
def decode_erc20_asset_data(asset_data: str) -> ERC20AssetData:
|
||||
# docstring considered all one line by pylint: disable=line-too-long
|
||||
"""Decode an ERC20 assetData hex string.
|
||||
|
||||
:param asset_data: String produced by prior call to encode_erc20_asset_data()
|
||||
|
||||
>>> decode_erc20_asset_data("0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48")
|
||||
{'asset_proxy_id': '0xf47261b0', 'token_address': '0x1dc4c1cefef38a777b15aa20260a54e584b16c48'}
|
||||
""" # noqa: E501 (line too long)
|
||||
assert_is_string(asset_data, "asset_data")
|
||||
|
||||
if len(asset_data) < ERC20_ASSET_DATA_BYTE_LENGTH:
|
||||
raise ValueError(
|
||||
"Could not decode ERC20 Proxy Data. Expected length of encoded"
|
||||
+ f" data to be at least {str(ERC20_ASSET_DATA_BYTE_LENGTH)}."
|
||||
+ f" Got {str(len(asset_data))}."
|
||||
)
|
||||
|
||||
asset_proxy_id: str = asset_data[0:10]
|
||||
if asset_proxy_id != abi_utils.method_id("ERC20Token", ["address"]):
|
||||
raise ValueError(
|
||||
"Could not decode ERC20 Proxy Data. Expected Asset Proxy Id to be"
|
||||
+ f" ERC20 ({abi_utils.method_id('ERC20Token', ['address'])})"
|
||||
+ f" but got {asset_proxy_id}."
|
||||
)
|
||||
|
||||
# workaround for https://github.com/PyCQA/pylint/issues/1498
|
||||
# pylint: disable=unsubscriptable-object
|
||||
token_address = eth_abi.decode_abi(
|
||||
["address"], bytes.fromhex(asset_data[SELECTOR_LENGTH:])
|
||||
)[0]
|
||||
|
||||
return {"asset_proxy_id": asset_proxy_id, "token_address": token_address}
|
||||
@@ -1,13 +0,0 @@
|
||||
"""Signature utilities."""
|
||||
|
||||
|
||||
def ec_sign_order_hash():
|
||||
"""Signs an orderHash.
|
||||
|
||||
Returns its elliptic curve signature and signature type. This method
|
||||
currently supports TestRPC, Geth, and Parity above and below v1.6.6.
|
||||
|
||||
>>> ec_sign_order_hash()
|
||||
'stub return value'
|
||||
"""
|
||||
return "stub return value"
|
||||
@@ -0,0 +1,7 @@
|
||||
from distutils.core import Command
|
||||
|
||||
class clean(Command):
|
||||
def initialize_options(self: clean) -> None: ...
|
||||
def finalize_options(self: clean) -> None: ...
|
||||
def run(self: clean) -> None: ...
|
||||
...
|
||||
1
python-packages/order_utils/stubs/pytest/raises.pyi
Normal file
1
python-packages/order_utils/stubs/pytest/raises.pyi
Normal file
@@ -0,0 +1 @@
|
||||
def raises(exception: Exception) -> ExceptionInfo: ...
|
||||
@@ -0,0 +1,6 @@
|
||||
from distutils.dist import Distribution
|
||||
from typing import Any
|
||||
|
||||
def setup(**attrs: Any) -> Distribution: ...
|
||||
|
||||
class Command: ...
|
||||
@@ -0,0 +1,3 @@
|
||||
from setuptools import Command
|
||||
|
||||
class test(Command): ...
|
||||
10
python-packages/order_utils/stubs/web3/__init__.pyi
Normal file
10
python-packages/order_utils/stubs/web3/__init__.pyi
Normal file
@@ -0,0 +1,10 @@
|
||||
from typing import Optional, Union
|
||||
|
||||
class Web3:
|
||||
@staticmethod
|
||||
def sha3(
|
||||
primitive: Optional[Union[bytes, int, None]] = None,
|
||||
text: Optional[str] = None,
|
||||
hexstr: Optional[str] = None
|
||||
) -> bytes: ...
|
||||
...
|
||||
53
python-packages/order_utils/test/test_abi_utils.py
Normal file
53
python-packages/order_utils/test/test_abi_utils.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""Tests of 0x.abi_utils."""
|
||||
|
||||
import pytest
|
||||
|
||||
from zero_ex.dev_utils.abi_utils import (
|
||||
elementary_name,
|
||||
event_id,
|
||||
method_id,
|
||||
parse_signature,
|
||||
simple_encode,
|
||||
)
|
||||
|
||||
|
||||
def test_parse_signature_type_error():
|
||||
"""Test that passing in wrong types raises TypeError."""
|
||||
with pytest.raises(TypeError):
|
||||
parse_signature(123)
|
||||
|
||||
|
||||
def test_parse_signature_bad_input():
|
||||
"""Test that passing a non-signature string raises a ValueError."""
|
||||
with pytest.raises(ValueError):
|
||||
parse_signature("a string that's not even close to a signature")
|
||||
|
||||
|
||||
def test_elementary_name_type_error():
|
||||
"""Test that passing in wrong types raises TypeError."""
|
||||
with pytest.raises(TypeError):
|
||||
elementary_name(123)
|
||||
|
||||
|
||||
def test_event_id_type_error():
|
||||
"""Test that passing in wrong types raises TypeError."""
|
||||
with pytest.raises(TypeError):
|
||||
event_id(123, [])
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
event_id("valid string", 123)
|
||||
|
||||
|
||||
def test_method_id_type_error():
|
||||
"""Test that passing in wrong types raises TypeError."""
|
||||
with pytest.raises(TypeError):
|
||||
method_id(123, [])
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
method_id("ERC20Token", 123)
|
||||
|
||||
|
||||
def test_simple_encode_type_error():
|
||||
"""Test that passing in wrong types raises TypeError."""
|
||||
with pytest.raises(TypeError):
|
||||
simple_encode(123)
|
||||
35
python-packages/order_utils/test/test_asset_data_utils.py
Normal file
35
python-packages/order_utils/test/test_asset_data_utils.py
Normal file
@@ -0,0 +1,35 @@
|
||||
"""Tests of 0x.order_utils.asset_data_utils."""
|
||||
|
||||
import pytest
|
||||
|
||||
from zero_ex.order_utils.asset_data_utils import (
|
||||
encode_erc20_asset_data,
|
||||
decode_erc20_asset_data,
|
||||
ERC20_ASSET_DATA_BYTE_LENGTH,
|
||||
)
|
||||
|
||||
|
||||
def test_encode_erc20_asset_data_type_error():
|
||||
"""Test that passing in a non-string raises a TypeError."""
|
||||
with pytest.raises(TypeError):
|
||||
encode_erc20_asset_data(123)
|
||||
|
||||
|
||||
def test_decode_erc20_asset_data_type_error():
|
||||
"""Test that passing in a non-string raises a TypeError."""
|
||||
with pytest.raises(TypeError):
|
||||
decode_erc20_asset_data(123)
|
||||
|
||||
|
||||
def test_decode_erc20_asset_data_too_short():
|
||||
"""Test that passing an insufficiently long string raises a ValueError."""
|
||||
with pytest.raises(ValueError):
|
||||
decode_erc20_asset_data(" " * (ERC20_ASSET_DATA_BYTE_LENGTH - 1))
|
||||
|
||||
|
||||
def test_decode_erc20_asset_data_invalid_proxy_id():
|
||||
"""Test that passing data with an invalid proxy ID raises a ValueError."""
|
||||
with pytest.raises(ValueError):
|
||||
decode_erc20_asset_data(
|
||||
"0xffffffff" + (" " * ERC20_ASSET_DATA_BYTE_LENGTH)
|
||||
)
|
||||
@@ -1,10 +1,24 @@
|
||||
"""Exercise doctests for order_utils module."""
|
||||
|
||||
from doctest import testmod
|
||||
from zero_ex.order_utils import signature_utils
|
||||
|
||||
from zero_ex.dev_utils import abi_utils, type_assertions
|
||||
from zero_ex.order_utils import asset_data_utils
|
||||
|
||||
|
||||
def test_doctest():
|
||||
"""Invoke doctest on the module."""
|
||||
(failure_count, _) = testmod(signature_utils)
|
||||
def test_doctest_asset_data_utils():
|
||||
"""Invoke doctest on the asset_data_utils module."""
|
||||
(failure_count, _) = testmod(asset_data_utils)
|
||||
assert failure_count == 0
|
||||
|
||||
|
||||
def test_doctest_abi_utils():
|
||||
"""Invoke doctest on the abi_utils module."""
|
||||
(failure_count, _) = testmod(abi_utils)
|
||||
assert failure_count == 0
|
||||
|
||||
|
||||
def test_doctest_type_assertions():
|
||||
"""Invoke doctest on the type_assertions module."""
|
||||
(failure_count, _) = testmod(type_assertions)
|
||||
assert failure_count == 0
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
"""Tests of 0x.order_utils.signature_utils.*."""
|
||||
|
||||
from zero_ex.order_utils.signature_utils import ec_sign_order_hash
|
||||
|
||||
|
||||
def test_ec_sign_order_hash():
|
||||
"""Test the signing of order hashes."""
|
||||
assert ec_sign_order_hash() == "stub return value"
|
||||
Reference in New Issue
Block a user