Replace assetDataUtils with DevUtilsContract wherever possible (#2304)
* Replace assetDataUtils with DevUtilsContract wherever possible Does not replace from @0x/instant and some @0x/order-utils uses * Add revertIfInvalidAssetData to LibAssetData This is needed to replace `assetDataUtils.decodeAssetDataOrThrow`. Because it's used in packages and not only contracts, we should wait to deploy the updated contract so we can update `@0x/contract-artifacts`, `@0x/abi-gen-wrappers`, and `@0x/contract-wrappers` first. * remove usages of signatureUtils * fix test for optimised encoding * refactor @0x/contracts-integrations * update changelogs * Move @0x/contracts-dev-utils from devDependencies to dependencies It is exported as part of the package
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { CoordinatorContract, SignedCoordinatorApproval } from '@0x/contracts-coordinator';
|
||||
import { DevUtilsContract } from '@0x/contracts-dev-utils';
|
||||
import {
|
||||
BlockchainBalanceStore,
|
||||
constants as exchangeConstants,
|
||||
@@ -10,8 +11,16 @@ import {
|
||||
ExchangeFunctionName,
|
||||
LocalBalanceStore,
|
||||
} from '@0x/contracts-exchange';
|
||||
import { blockchainTests, constants, expect, hexConcat, hexSlice, verifyEvents } from '@0x/contracts-test-utils';
|
||||
import { assetDataUtils, CoordinatorRevertErrors, orderHashUtils, transactionHashUtils } from '@0x/order-utils';
|
||||
import {
|
||||
blockchainTests,
|
||||
constants,
|
||||
expect,
|
||||
hexConcat,
|
||||
hexSlice,
|
||||
provider,
|
||||
verifyEvents,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { CoordinatorRevertErrors, orderHashUtils, transactionHashUtils } from '@0x/order-utils';
|
||||
import { SignedOrder, SignedZeroExTransaction } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||
@@ -26,6 +35,7 @@ blockchainTests.resets('Coordinator integration tests', env => {
|
||||
let deployment: DeploymentManager;
|
||||
let coordinator: CoordinatorContract;
|
||||
let balanceStore: BlockchainBalanceStore;
|
||||
let devUtils: DevUtilsContract;
|
||||
|
||||
let maker: Maker;
|
||||
let taker: Actor;
|
||||
@@ -38,6 +48,7 @@ blockchainTests.resets('Coordinator integration tests', env => {
|
||||
numErc1155TokensToDeploy: 0,
|
||||
});
|
||||
coordinator = await deployCoordinatorAsync(deployment, env);
|
||||
devUtils = new DevUtilsContract(constants.NULL_ADDRESS, provider);
|
||||
|
||||
const [makerToken, takerToken, makerFeeToken, takerFeeToken] = deployment.tokens.erc20;
|
||||
|
||||
@@ -53,10 +64,10 @@ blockchainTests.resets('Coordinator integration tests', env => {
|
||||
orderConfig: {
|
||||
senderAddress: coordinator.address,
|
||||
feeRecipientAddress: feeRecipient.address,
|
||||
makerAssetData: assetDataUtils.encodeERC20AssetData(makerToken.address),
|
||||
takerAssetData: assetDataUtils.encodeERC20AssetData(takerToken.address),
|
||||
makerFeeAssetData: assetDataUtils.encodeERC20AssetData(makerFeeToken.address),
|
||||
takerFeeAssetData: assetDataUtils.encodeERC20AssetData(takerFeeToken.address),
|
||||
makerAssetData: await devUtils.encodeERC20AssetData.callAsync(makerToken.address),
|
||||
takerAssetData: await devUtils.encodeERC20AssetData.callAsync(takerToken.address),
|
||||
makerFeeAssetData: await devUtils.encodeERC20AssetData.callAsync(makerFeeToken.address),
|
||||
takerFeeAssetData: await devUtils.encodeERC20AssetData.callAsync(takerFeeToken.address),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -77,30 +88,40 @@ blockchainTests.resets('Coordinator integration tests', env => {
|
||||
);
|
||||
});
|
||||
|
||||
function simulateFills(
|
||||
async function simulateFillsAsync(
|
||||
orders: SignedOrder[],
|
||||
txReceipt: TransactionReceiptWithDecodedLogs,
|
||||
msgValue?: BigNumber,
|
||||
): LocalBalanceStore {
|
||||
): Promise<LocalBalanceStore> {
|
||||
let remainingValue = msgValue || constants.ZERO_AMOUNT;
|
||||
const localBalanceStore = LocalBalanceStore.create(balanceStore);
|
||||
const localBalanceStore = LocalBalanceStore.create(devUtils, balanceStore);
|
||||
// Transaction gas cost
|
||||
localBalanceStore.burnGas(txReceipt.from, DeploymentManager.gasPrice.times(txReceipt.gasUsed));
|
||||
|
||||
for (const order of orders) {
|
||||
// Taker -> Maker
|
||||
localBalanceStore.transferAsset(taker.address, maker.address, order.takerAssetAmount, order.takerAssetData);
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
taker.address,
|
||||
maker.address,
|
||||
order.takerAssetAmount,
|
||||
order.takerAssetData,
|
||||
);
|
||||
// Maker -> Taker
|
||||
localBalanceStore.transferAsset(maker.address, taker.address, order.makerAssetAmount, order.makerAssetData);
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
maker.address,
|
||||
taker.address,
|
||||
order.makerAssetAmount,
|
||||
order.makerAssetData,
|
||||
);
|
||||
// Taker -> Fee Recipient
|
||||
localBalanceStore.transferAsset(
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
taker.address,
|
||||
feeRecipient.address,
|
||||
order.takerFee,
|
||||
order.takerFeeAssetData,
|
||||
);
|
||||
// Maker -> Fee Recipient
|
||||
localBalanceStore.transferAsset(
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
maker.address,
|
||||
feeRecipient.address,
|
||||
order.makerFee,
|
||||
@@ -116,11 +137,11 @@ blockchainTests.resets('Coordinator integration tests', env => {
|
||||
);
|
||||
remainingValue = remainingValue.minus(DeploymentManager.protocolFee);
|
||||
} else {
|
||||
localBalanceStore.transferAsset(
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
taker.address,
|
||||
deployment.staking.stakingProxy.address,
|
||||
DeploymentManager.protocolFee,
|
||||
assetDataUtils.encodeERC20AssetData(deployment.tokens.weth.address),
|
||||
await devUtils.encodeERC20AssetData.callAsync(deployment.tokens.weth.address),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -174,7 +195,7 @@ blockchainTests.resets('Coordinator integration tests', env => {
|
||||
{ from: taker.address, value: DeploymentManager.protocolFee },
|
||||
);
|
||||
|
||||
const expectedBalances = simulateFills([order], txReceipt, DeploymentManager.protocolFee);
|
||||
const expectedBalances = await simulateFillsAsync([order], txReceipt, DeploymentManager.protocolFee);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
verifyEvents(txReceipt, [expectedFillEvent(order)], ExchangeEvents.Fill);
|
||||
@@ -189,7 +210,7 @@ blockchainTests.resets('Coordinator integration tests', env => {
|
||||
{ from: feeRecipient.address, value: DeploymentManager.protocolFee },
|
||||
);
|
||||
|
||||
const expectedBalances = simulateFills([order], txReceipt, DeploymentManager.protocolFee);
|
||||
const expectedBalances = await simulateFillsAsync([order], txReceipt, DeploymentManager.protocolFee);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
verifyEvents(txReceipt, [expectedFillEvent(order)], ExchangeEvents.Fill);
|
||||
@@ -204,7 +225,11 @@ blockchainTests.resets('Coordinator integration tests', env => {
|
||||
{ from: feeRecipient.address, value: DeploymentManager.protocolFee.plus(1) },
|
||||
);
|
||||
|
||||
const expectedBalances = simulateFills([order], txReceipt, DeploymentManager.protocolFee.plus(1));
|
||||
const expectedBalances = await simulateFillsAsync(
|
||||
[order],
|
||||
txReceipt,
|
||||
DeploymentManager.protocolFee.plus(1),
|
||||
);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
verifyEvents(txReceipt, [expectedFillEvent(order)], ExchangeEvents.Fill);
|
||||
@@ -219,7 +244,7 @@ blockchainTests.resets('Coordinator integration tests', env => {
|
||||
{ from: feeRecipient.address },
|
||||
);
|
||||
|
||||
const expectedBalances = simulateFills([order], txReceipt);
|
||||
const expectedBalances = await simulateFillsAsync([order], txReceipt);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
verifyEvents(txReceipt, [expectedFillEvent(order)], ExchangeEvents.Fill);
|
||||
@@ -234,7 +259,7 @@ blockchainTests.resets('Coordinator integration tests', env => {
|
||||
{ from: feeRecipient.address, value: new BigNumber(1) },
|
||||
);
|
||||
|
||||
const expectedBalances = simulateFills([order], txReceipt, new BigNumber(1));
|
||||
const expectedBalances = await simulateFillsAsync([order], txReceipt, new BigNumber(1));
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
verifyEvents(txReceipt, [expectedFillEvent(order)], ExchangeEvents.Fill);
|
||||
@@ -318,7 +343,7 @@ blockchainTests.resets('Coordinator integration tests', env => {
|
||||
{ from: taker.address, value },
|
||||
);
|
||||
|
||||
const expectedBalances = simulateFills(orders, txReceipt, value);
|
||||
const expectedBalances = await simulateFillsAsync(orders, txReceipt, value);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
verifyEvents(txReceipt, orders.map(order => expectedFillEvent(order)), ExchangeEvents.Fill);
|
||||
@@ -334,7 +359,7 @@ blockchainTests.resets('Coordinator integration tests', env => {
|
||||
{ from: feeRecipient.address, value },
|
||||
);
|
||||
|
||||
const expectedBalances = simulateFills(orders, txReceipt, value);
|
||||
const expectedBalances = await simulateFillsAsync(orders, txReceipt, value);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
verifyEvents(txReceipt, orders.map(order => expectedFillEvent(order)), ExchangeEvents.Fill);
|
||||
@@ -350,7 +375,7 @@ blockchainTests.resets('Coordinator integration tests', env => {
|
||||
{ from: feeRecipient.address, value },
|
||||
);
|
||||
|
||||
const expectedBalances = simulateFills(orders, txReceipt, value);
|
||||
const expectedBalances = await simulateFillsAsync(orders, txReceipt, value);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
verifyEvents(txReceipt, orders.map(order => expectedFillEvent(order)), ExchangeEvents.Fill);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DevUtilsContract } from '@0x/contracts-dev-utils';
|
||||
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
|
||||
import { DummyERC721TokenContract } from '@0x/contracts-erc721';
|
||||
import {
|
||||
@@ -13,9 +14,10 @@ import {
|
||||
expect,
|
||||
getLatestBlockTimestampAsync,
|
||||
getPercentageOfValue,
|
||||
provider,
|
||||
toBaseUnitAmount,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { assetDataUtils, ForwarderRevertErrors } from '@0x/order-utils';
|
||||
import { ForwarderRevertErrors } from '@0x/order-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { Actor, actorAddressesByName, FeeRecipient, Maker } from '../actors';
|
||||
@@ -24,6 +26,8 @@ import { DeploymentManager } from '../utils/deployment_manager';
|
||||
import { deployForwarderAsync } from './deploy_forwarder';
|
||||
import { ForwarderTestFactory } from './forwarder_test_factory';
|
||||
|
||||
const devUtils = new DevUtilsContract(constants.NULL_ADDRESS, provider);
|
||||
|
||||
blockchainTests('Forwarder integration tests', env => {
|
||||
let deployment: DeploymentManager;
|
||||
let forwarder: ForwarderContract;
|
||||
@@ -53,8 +57,8 @@ blockchainTests('Forwarder integration tests', env => {
|
||||
|
||||
[makerToken, makerFeeToken, anotherErc20Token] = deployment.tokens.erc20;
|
||||
[erc721Token] = deployment.tokens.erc721;
|
||||
wethAssetData = assetDataUtils.encodeERC20AssetData(deployment.tokens.weth.address);
|
||||
makerAssetData = assetDataUtils.encodeERC20AssetData(makerToken.address);
|
||||
wethAssetData = await devUtils.encodeERC20AssetData.callAsync(deployment.tokens.weth.address);
|
||||
makerAssetData = await devUtils.encodeERC20AssetData.callAsync(makerToken.address);
|
||||
|
||||
taker = new Actor({ name: 'Taker', deployment });
|
||||
orderFeeRecipient = new FeeRecipient({
|
||||
@@ -75,7 +79,7 @@ blockchainTests('Forwarder integration tests', env => {
|
||||
makerAssetData,
|
||||
takerAssetData: wethAssetData,
|
||||
takerFee: constants.ZERO_AMOUNT,
|
||||
makerFeeAssetData: assetDataUtils.encodeERC20AssetData(makerFeeToken.address),
|
||||
makerFeeAssetData: await devUtils.encodeERC20AssetData.callAsync(makerFeeToken.address),
|
||||
takerFeeAssetData: wethAssetData,
|
||||
},
|
||||
});
|
||||
@@ -106,6 +110,7 @@ blockchainTests('Forwarder integration tests', env => {
|
||||
taker,
|
||||
orderFeeRecipient,
|
||||
forwarderFeeRecipient,
|
||||
devUtils,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -166,7 +171,7 @@ blockchainTests('Forwarder integration tests', env => {
|
||||
await testFactory.marketSellTestAsync(orders, 1.34);
|
||||
});
|
||||
it('should fail to fill an order with a percentage fee if the asset proxy is not yet approved', async () => {
|
||||
const unapprovedAsset = assetDataUtils.encodeERC20AssetData(anotherErc20Token.address);
|
||||
const unapprovedAsset = await devUtils.encodeERC20AssetData.callAsync(anotherErc20Token.address);
|
||||
const order = await maker.signOrderAsync({
|
||||
makerAssetData: unapprovedAsset,
|
||||
takerFee: toBaseUnitAmount(2),
|
||||
@@ -186,7 +191,7 @@ blockchainTests('Forwarder integration tests', env => {
|
||||
},
|
||||
);
|
||||
|
||||
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||
const expectedBalances = LocalBalanceStore.create(devUtils, balanceStore);
|
||||
expectedBalances.burnGas(tx.from, DeploymentManager.gasPrice.times(tx.gasUsed));
|
||||
|
||||
// Verify balances
|
||||
@@ -236,7 +241,7 @@ blockchainTests('Forwarder integration tests', env => {
|
||||
});
|
||||
it('should fill orders with different makerAssetData', async () => {
|
||||
const firstOrder = await maker.signOrderAsync();
|
||||
const secondOrderMakerAssetData = assetDataUtils.encodeERC20AssetData(anotherErc20Token.address);
|
||||
const secondOrderMakerAssetData = await devUtils.encodeERC20AssetData.callAsync(anotherErc20Token.address);
|
||||
const secondOrder = await maker.signOrderAsync({
|
||||
makerAssetData: secondOrderMakerAssetData,
|
||||
});
|
||||
@@ -245,7 +250,7 @@ blockchainTests('Forwarder integration tests', env => {
|
||||
await testFactory.marketSellTestAsync(orders, 1.5);
|
||||
});
|
||||
it('should fail to fill an order with a fee denominated in an asset other than makerAsset or WETH', async () => {
|
||||
const takerFeeAssetData = assetDataUtils.encodeERC20AssetData(anotherErc20Token.address);
|
||||
const takerFeeAssetData = await devUtils.encodeERC20AssetData.callAsync(anotherErc20Token.address);
|
||||
const order = await maker.signOrderAsync({
|
||||
takerFeeAssetData,
|
||||
takerFee: toBaseUnitAmount(1),
|
||||
@@ -336,7 +341,7 @@ blockchainTests('Forwarder integration tests', env => {
|
||||
});
|
||||
it('should buy exactly makerAssetBuyAmount in orders with different makerAssetData', async () => {
|
||||
const firstOrder = await maker.signOrderAsync();
|
||||
const secondOrderMakerAssetData = assetDataUtils.encodeERC20AssetData(anotherErc20Token.address);
|
||||
const secondOrderMakerAssetData = await devUtils.encodeERC20AssetData.callAsync(anotherErc20Token.address);
|
||||
const secondOrder = await maker.signOrderAsync({
|
||||
makerAssetData: secondOrderMakerAssetData,
|
||||
});
|
||||
@@ -385,7 +390,7 @@ blockchainTests('Forwarder integration tests', env => {
|
||||
it('should buy an ERC721 asset from a single order', async () => {
|
||||
const erc721Order = await maker.signOrderAsync({
|
||||
makerAssetAmount: new BigNumber(1),
|
||||
makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, nftId),
|
||||
makerAssetData: await devUtils.encodeERC721AssetData.callAsync(erc721Token.address, nftId),
|
||||
takerFeeAssetData: wethAssetData,
|
||||
});
|
||||
await testFactory.marketBuyTestAsync([erc721Order], 1);
|
||||
@@ -393,14 +398,14 @@ blockchainTests('Forwarder integration tests', env => {
|
||||
it('should buy an ERC721 asset and pay a WETH fee', async () => {
|
||||
const erc721orderWithWethFee = await maker.signOrderAsync({
|
||||
makerAssetAmount: new BigNumber(1),
|
||||
makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, nftId),
|
||||
makerAssetData: await devUtils.encodeERC721AssetData.callAsync(erc721Token.address, nftId),
|
||||
takerFee: toBaseUnitAmount(1),
|
||||
takerFeeAssetData: wethAssetData,
|
||||
});
|
||||
await testFactory.marketBuyTestAsync([erc721orderWithWethFee], 1);
|
||||
});
|
||||
it('should fail to fill an order with a fee denominated in an asset other than makerAsset or WETH', async () => {
|
||||
const takerFeeAssetData = assetDataUtils.encodeERC20AssetData(anotherErc20Token.address);
|
||||
const takerFeeAssetData = await devUtils.encodeERC20AssetData.callAsync(anotherErc20Token.address);
|
||||
const order = await maker.signOrderAsync({
|
||||
takerFeeAssetData,
|
||||
takerFee: toBaseUnitAmount(1),
|
||||
@@ -491,11 +496,21 @@ blockchainTests('Forwarder integration tests', env => {
|
||||
);
|
||||
|
||||
// Compute expected balances
|
||||
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||
expectedBalances.transferAsset(maker.address, taker.address, makerAssetFillAmount, makerAssetData);
|
||||
const expectedBalances = LocalBalanceStore.create(devUtils, balanceStore);
|
||||
await expectedBalances.transferAssetAsync(
|
||||
maker.address,
|
||||
taker.address,
|
||||
makerAssetFillAmount,
|
||||
makerAssetData,
|
||||
);
|
||||
expectedBalances.wrapEth(taker.address, deployment.tokens.weth.address, ethValue);
|
||||
expectedBalances.transferAsset(taker.address, maker.address, primaryTakerAssetFillAmount, wethAssetData);
|
||||
expectedBalances.transferAsset(
|
||||
await expectedBalances.transferAssetAsync(
|
||||
taker.address,
|
||||
maker.address,
|
||||
primaryTakerAssetFillAmount,
|
||||
wethAssetData,
|
||||
);
|
||||
await expectedBalances.transferAssetAsync(
|
||||
taker.address,
|
||||
deployment.staking.stakingProxy.address,
|
||||
DeploymentManager.protocolFee,
|
||||
@@ -537,15 +552,15 @@ blockchainTests('Forwarder integration tests', env => {
|
||||
);
|
||||
|
||||
// Compute expected balances
|
||||
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||
expectedBalances.transferAsset(maker.address, taker.address, makerAssetFillAmount, makerAssetData);
|
||||
const expectedBalances = LocalBalanceStore.create(devUtils, balanceStore);
|
||||
expectedBalances.transferAssetAsync(maker.address, taker.address, makerAssetFillAmount, makerAssetData);
|
||||
expectedBalances.wrapEth(
|
||||
taker.address,
|
||||
deployment.tokens.weth.address,
|
||||
takerAssetFillAmount.plus(DeploymentManager.protocolFee),
|
||||
);
|
||||
expectedBalances.transferAsset(taker.address, maker.address, takerAssetFillAmount, wethAssetData);
|
||||
expectedBalances.transferAsset(
|
||||
expectedBalances.transferAssetAsync(taker.address, maker.address, takerAssetFillAmount, wethAssetData);
|
||||
expectedBalances.transferAssetAsync(
|
||||
taker.address,
|
||||
deployment.staking.stakingProxy.address,
|
||||
DeploymentManager.protocolFee,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DevUtilsContract } from '@0x/contracts-dev-utils';
|
||||
import { BlockchainBalanceStore, LocalBalanceStore } from '@0x/contracts-exchange';
|
||||
import { ForwarderContract } from '@0x/contracts-exchange-forwarder';
|
||||
import { constants, expect, getPercentageOfValue, OrderStatus } from '@0x/contracts-test-utils';
|
||||
@@ -33,6 +34,7 @@ export class ForwarderTestFactory {
|
||||
private readonly _taker: Actor,
|
||||
private readonly _orderFeeRecipient: FeeRecipient,
|
||||
private readonly _forwarderFeeRecipient: FeeRecipient,
|
||||
private readonly _devUtils: DevUtilsContract,
|
||||
) {}
|
||||
|
||||
public async marketBuyTestAsync(
|
||||
@@ -157,7 +159,7 @@ export class ForwarderTestFactory {
|
||||
options: Partial<MarketBuyOptions>,
|
||||
): Promise<ForwarderFillState> {
|
||||
await this._balanceStore.updateBalancesAsync();
|
||||
const balances = LocalBalanceStore.create(this._balanceStore);
|
||||
const balances = LocalBalanceStore.create(this._devUtils, this._balanceStore);
|
||||
const currentTotal = {
|
||||
wethSpentAmount: constants.ZERO_AMOUNT,
|
||||
makerAssetAcquiredAmount: constants.ZERO_AMOUNT,
|
||||
@@ -173,7 +175,7 @@ export class ForwarderTestFactory {
|
||||
continue;
|
||||
}
|
||||
|
||||
const { wethSpentAmount, makerAssetAcquiredAmount } = this._simulateSingleFill(
|
||||
const { wethSpentAmount, makerAssetAcquiredAmount } = await this._simulateSingleFillAsync(
|
||||
balances,
|
||||
order,
|
||||
ordersInfoBefore[i].orderTakerAssetFilledAmount,
|
||||
@@ -197,12 +199,12 @@ export class ForwarderTestFactory {
|
||||
return { ...currentTotal, balances };
|
||||
}
|
||||
|
||||
private _simulateSingleFill(
|
||||
private async _simulateSingleFillAsync(
|
||||
balances: LocalBalanceStore,
|
||||
order: SignedOrder,
|
||||
takerAssetFilled: BigNumber,
|
||||
fillFraction: number,
|
||||
): ForwarderFillState {
|
||||
): Promise<ForwarderFillState> {
|
||||
let { makerAssetAmount, takerAssetAmount, makerFee, takerFee } = order;
|
||||
makerAssetAmount = makerAssetAmount.times(fillFraction).integerValue(BigNumber.ROUND_CEIL);
|
||||
takerAssetAmount = takerAssetAmount.times(fillFraction).integerValue(BigNumber.ROUND_CEIL);
|
||||
@@ -236,27 +238,42 @@ export class ForwarderTestFactory {
|
||||
// (In reality this is done all at once, but we simulate it order by order)
|
||||
|
||||
// Maker -> Forwarder
|
||||
balances.transferAsset(this._maker.address, this._forwarder.address, makerAssetAmount, order.makerAssetData);
|
||||
await balances.transferAssetAsync(
|
||||
this._maker.address,
|
||||
this._forwarder.address,
|
||||
makerAssetAmount,
|
||||
order.makerAssetData,
|
||||
);
|
||||
// Maker -> Order fee recipient
|
||||
balances.transferAsset(this._maker.address, this._orderFeeRecipient.address, makerFee, order.makerFeeAssetData);
|
||||
await balances.transferAssetAsync(
|
||||
this._maker.address,
|
||||
this._orderFeeRecipient.address,
|
||||
makerFee,
|
||||
order.makerFeeAssetData,
|
||||
);
|
||||
// Forwarder -> Maker
|
||||
balances.transferAsset(this._forwarder.address, this._maker.address, takerAssetAmount, order.takerAssetData);
|
||||
await balances.transferAssetAsync(
|
||||
this._forwarder.address,
|
||||
this._maker.address,
|
||||
takerAssetAmount,
|
||||
order.takerAssetData,
|
||||
);
|
||||
// Forwarder -> Order fee recipient
|
||||
balances.transferAsset(
|
||||
await balances.transferAssetAsync(
|
||||
this._forwarder.address,
|
||||
this._orderFeeRecipient.address,
|
||||
takerFee,
|
||||
order.takerFeeAssetData,
|
||||
);
|
||||
// Forwarder pays the protocol fee in WETH
|
||||
balances.transferAsset(
|
||||
await balances.transferAssetAsync(
|
||||
this._forwarder.address,
|
||||
this._deployment.staking.stakingProxy.address,
|
||||
DeploymentManager.protocolFee,
|
||||
order.takerAssetData,
|
||||
);
|
||||
// Forwarder gives acquired maker asset to taker
|
||||
balances.transferAsset(
|
||||
await balances.transferAssetAsync(
|
||||
this._forwarder.address,
|
||||
this._taker.address,
|
||||
makerAssetAcquiredAmount,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { artifacts as assetProxyArtifacts } from '@0x/contracts-asset-proxy';
|
||||
import { DevUtilsContract } from '@0x/contracts-dev-utils';
|
||||
import { IERC20TokenEvents, IERC20TokenTransferEventArgs } from '@0x/contracts-erc20';
|
||||
import {
|
||||
artifacts as exchangeArtifacts,
|
||||
@@ -16,11 +17,12 @@ import {
|
||||
expect,
|
||||
getLatestBlockTimestampAsync,
|
||||
Numberish,
|
||||
provider,
|
||||
toBaseUnitAmount,
|
||||
TransactionHelper,
|
||||
verifyEvents,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { assetDataUtils, ExchangeRevertErrors, orderHashUtils } from '@0x/order-utils';
|
||||
import { ExchangeRevertErrors, orderHashUtils } from '@0x/order-utils';
|
||||
import { FillResults, OrderStatus, SignedOrder } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||
@@ -32,6 +34,7 @@ import { DeploymentManager } from '../utils/deployment_manager';
|
||||
|
||||
const { addFillResults, safeGetPartialAmountFloor } = ReferenceFunctions;
|
||||
|
||||
const devUtils = new DevUtilsContract(constants.NULL_ADDRESS, provider);
|
||||
// tslint:disable:no-unnecessary-type-assertion
|
||||
blockchainTests.resets('Exchange wrappers', env => {
|
||||
let maker: Maker;
|
||||
@@ -67,10 +70,10 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
name: 'market maker',
|
||||
deployment,
|
||||
orderConfig: {
|
||||
makerAssetData: assetDataUtils.encodeERC20AssetData(deployment.tokens.erc20[0].address),
|
||||
takerAssetData: assetDataUtils.encodeERC20AssetData(deployment.tokens.erc20[1].address),
|
||||
makerFeeAssetData: assetDataUtils.encodeERC20AssetData(deployment.tokens.erc20[2].address),
|
||||
takerFeeAssetData: assetDataUtils.encodeERC20AssetData(deployment.tokens.erc20[2].address),
|
||||
makerAssetData: await devUtils.encodeERC20AssetData.callAsync(deployment.tokens.erc20[0].address),
|
||||
takerAssetData: await devUtils.encodeERC20AssetData.callAsync(deployment.tokens.erc20[1].address),
|
||||
makerFeeAssetData: await devUtils.encodeERC20AssetData.callAsync(deployment.tokens.erc20[2].address),
|
||||
takerFeeAssetData: await devUtils.encodeERC20AssetData.callAsync(deployment.tokens.erc20[2].address),
|
||||
feeRecipientAddress: feeRecipient,
|
||||
},
|
||||
});
|
||||
@@ -106,9 +109,9 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
|
||||
await blockchainBalances.updateBalancesAsync();
|
||||
|
||||
initialLocalBalances = LocalBalanceStore.create(blockchainBalances);
|
||||
initialLocalBalances = LocalBalanceStore.create(devUtils, blockchainBalances);
|
||||
|
||||
wethAssetData = assetDataUtils.encodeERC20AssetData(deployment.tokens.weth.address);
|
||||
wethAssetData = await devUtils.encodeERC20AssetData.callAsync(deployment.tokens.weth.address);
|
||||
|
||||
txHelper = new TransactionHelper(env.web3Wrapper, {
|
||||
...assetProxyArtifacts,
|
||||
@@ -118,7 +121,7 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
localBalances = LocalBalanceStore.create(initialLocalBalances);
|
||||
localBalances = LocalBalanceStore.create(devUtils, initialLocalBalances);
|
||||
});
|
||||
|
||||
interface SignedOrderWithValidity {
|
||||
@@ -126,9 +129,13 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
isValid: boolean;
|
||||
}
|
||||
|
||||
function simulateFill(signedOrder: SignedOrder, expectedFillResults: FillResults, shouldUseWeth: boolean): void {
|
||||
async function simulateFillAsync(
|
||||
signedOrder: SignedOrder,
|
||||
expectedFillResults: FillResults,
|
||||
shouldUseWeth: boolean,
|
||||
): Promise<void> {
|
||||
// taker -> maker
|
||||
localBalances.transferAsset(
|
||||
await localBalances.transferAssetAsync(
|
||||
taker.address,
|
||||
maker.address,
|
||||
expectedFillResults.takerAssetFilledAmount,
|
||||
@@ -136,7 +143,7 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
);
|
||||
|
||||
// maker -> taker
|
||||
localBalances.transferAsset(
|
||||
await localBalances.transferAssetAsync(
|
||||
maker.address,
|
||||
taker.address,
|
||||
expectedFillResults.makerAssetFilledAmount,
|
||||
@@ -144,7 +151,7 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
);
|
||||
|
||||
// maker -> feeRecipient
|
||||
localBalances.transferAsset(
|
||||
await localBalances.transferAssetAsync(
|
||||
maker.address,
|
||||
feeRecipient,
|
||||
expectedFillResults.makerFeePaid,
|
||||
@@ -152,7 +159,7 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
);
|
||||
|
||||
// taker -> feeRecipient
|
||||
localBalances.transferAsset(
|
||||
await localBalances.transferAssetAsync(
|
||||
taker.address,
|
||||
feeRecipient,
|
||||
expectedFillResults.takerFeePaid,
|
||||
@@ -161,7 +168,7 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
|
||||
// taker -> protocol fees
|
||||
if (shouldUseWeth) {
|
||||
localBalances.transferAsset(
|
||||
await localBalances.transferAssetAsync(
|
||||
taker.address,
|
||||
deployment.staking.stakingProxy.address,
|
||||
expectedFillResults.protocolFeePaid,
|
||||
@@ -332,7 +339,7 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
const shouldPayWethFees = DeploymentManager.protocolFee.gt(value);
|
||||
|
||||
// Simulate filling the order
|
||||
simulateFill(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
|
||||
// Ensure that the correct logs were emitted and that the balances are accurate.
|
||||
await assertResultsAsync(receipt, [{ signedOrder, expectedFillResults, shouldPayWethFees }]);
|
||||
@@ -430,7 +437,7 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
}
|
||||
|
||||
fillTestInfo.push({ signedOrder, expectedFillResults, shouldPayWethFees });
|
||||
simulateFill(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
}
|
||||
|
||||
const [fillResults, receipt] = await txHelper.getResultAndReceiptAsync(
|
||||
@@ -492,7 +499,7 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
}
|
||||
|
||||
fillTestInfo.push({ signedOrder, expectedFillResults, shouldPayWethFees });
|
||||
simulateFill(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
}
|
||||
|
||||
const [fillResults, receipt] = await txHelper.getResultAndReceiptAsync(
|
||||
@@ -583,7 +590,7 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
}
|
||||
|
||||
fillTestInfo.push({ signedOrder, expectedFillResults, shouldPayWethFees });
|
||||
simulateFill(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
} else {
|
||||
totalFillResults.push(nullFillResults);
|
||||
}
|
||||
@@ -696,7 +703,7 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
takerFillAmount,
|
||||
);
|
||||
|
||||
simulateFill(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
fillTestInfo.push({ signedOrder, expectedFillResults, shouldPayWethFees });
|
||||
|
||||
totalFillResults = addFillResults(totalFillResults, expectedFillResults);
|
||||
@@ -774,7 +781,9 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
});
|
||||
|
||||
it('should fill a signedOrder that does not use the same takerAssetAddress (eth protocol fee)', async () => {
|
||||
const differentTakerAssetData = assetDataUtils.encodeERC20AssetData(deployment.tokens.erc20[2].address);
|
||||
const differentTakerAssetData = await devUtils.encodeERC20AssetData.callAsync(
|
||||
deployment.tokens.erc20[2].address,
|
||||
);
|
||||
|
||||
signedOrders = [
|
||||
await maker.signOrderAsync(),
|
||||
@@ -795,7 +804,9 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
});
|
||||
|
||||
it('should fill a signedOrder that does not use the same takerAssetAddress (weth protocol fee)', async () => {
|
||||
const differentTakerAssetData = assetDataUtils.encodeERC20AssetData(deployment.tokens.erc20[2].address);
|
||||
const differentTakerAssetData = await devUtils.encodeERC20AssetData.callAsync(
|
||||
deployment.tokens.erc20[2].address,
|
||||
);
|
||||
|
||||
signedOrders = [
|
||||
await maker.signOrderAsync(),
|
||||
@@ -890,7 +901,7 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
makerAssetBought,
|
||||
);
|
||||
|
||||
simulateFill(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
fillTestInfo.push({ signedOrder, expectedFillResults, shouldPayWethFees });
|
||||
|
||||
totalFillResults = addFillResults(totalFillResults, expectedFillResults);
|
||||
@@ -968,7 +979,9 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
});
|
||||
|
||||
it('should fill a signedOrder that does not use the same makerAssetAddress (eth protocol fee)', async () => {
|
||||
const differentMakerAssetData = assetDataUtils.encodeERC20AssetData(deployment.tokens.erc20[2].address);
|
||||
const differentMakerAssetData = await devUtils.encodeERC20AssetData.callAsync(
|
||||
deployment.tokens.erc20[2].address,
|
||||
);
|
||||
|
||||
signedOrders = [
|
||||
await maker.signOrderAsync(),
|
||||
@@ -990,7 +1003,9 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
});
|
||||
|
||||
it('should fill a signedOrder that does not use the same makerAssetAddress (weth protocol fee)', async () => {
|
||||
const differentMakerAssetData = assetDataUtils.encodeERC20AssetData(deployment.tokens.erc20[2].address);
|
||||
const differentMakerAssetData = await devUtils.encodeERC20AssetData.callAsync(
|
||||
deployment.tokens.erc20[2].address,
|
||||
);
|
||||
|
||||
signedOrders = [
|
||||
await maker.signOrderAsync(),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DevUtilsContract } from '@0x/contracts-dev-utils';
|
||||
import { IERC20TokenEvents, IERC20TokenTransferEventArgs } from '@0x/contracts-erc20';
|
||||
import {
|
||||
BlockchainBalanceStore,
|
||||
@@ -13,8 +14,8 @@ import {
|
||||
IStakingEventsRewardsPaidEventArgs,
|
||||
IStakingEventsStakingPoolEarnedRewardsInEpochEventArgs,
|
||||
} from '@0x/contracts-staking';
|
||||
import { blockchainTests, constants, expect, toBaseUnitAmount, verifyEvents } from '@0x/contracts-test-utils';
|
||||
import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
|
||||
import { blockchainTests, constants, expect, provider, toBaseUnitAmount, verifyEvents } from '@0x/contracts-test-utils';
|
||||
import { orderHashUtils } from '@0x/order-utils';
|
||||
import { SignedOrder } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||
@@ -22,6 +23,7 @@ import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||
import { actorAddressesByName, FeeRecipient, Maker, OperatorStakerMaker, StakerKeeper, Taker } from '../actors';
|
||||
import { DeploymentManager } from '../utils/deployment_manager';
|
||||
|
||||
const devUtils = new DevUtilsContract(constants.NULL_ADDRESS, provider);
|
||||
blockchainTests.resets('fillOrder integration tests', env => {
|
||||
let deployment: DeploymentManager;
|
||||
let balanceStore: BlockchainBalanceStore;
|
||||
@@ -48,10 +50,10 @@ blockchainTests.resets('fillOrder integration tests', env => {
|
||||
});
|
||||
const orderConfig = {
|
||||
feeRecipientAddress: feeRecipient.address,
|
||||
makerAssetData: assetDataUtils.encodeERC20AssetData(makerToken.address),
|
||||
takerAssetData: assetDataUtils.encodeERC20AssetData(takerToken.address),
|
||||
makerFeeAssetData: assetDataUtils.encodeERC20AssetData(makerToken.address),
|
||||
takerFeeAssetData: assetDataUtils.encodeERC20AssetData(takerToken.address),
|
||||
makerAssetData: await devUtils.encodeERC20AssetData.callAsync(makerToken.address),
|
||||
takerAssetData: await devUtils.encodeERC20AssetData.callAsync(takerToken.address),
|
||||
makerFeeAssetData: await devUtils.encodeERC20AssetData.callAsync(makerToken.address),
|
||||
takerFeeAssetData: await devUtils.encodeERC20AssetData.callAsync(takerToken.address),
|
||||
makerFee: constants.ZERO_AMOUNT,
|
||||
takerFee: constants.ZERO_AMOUNT,
|
||||
};
|
||||
@@ -93,20 +95,30 @@ blockchainTests.resets('fillOrder integration tests', env => {
|
||||
await balanceStore.updateBalancesAsync();
|
||||
});
|
||||
|
||||
function simulateFill(
|
||||
async function simulateFillAsync(
|
||||
order: SignedOrder,
|
||||
txReceipt: TransactionReceiptWithDecodedLogs,
|
||||
msgValue?: BigNumber,
|
||||
): LocalBalanceStore {
|
||||
): Promise<LocalBalanceStore> {
|
||||
let remainingValue = msgValue !== undefined ? msgValue : DeploymentManager.protocolFee;
|
||||
const localBalanceStore = LocalBalanceStore.create(balanceStore);
|
||||
const localBalanceStore = LocalBalanceStore.create(devUtils, balanceStore);
|
||||
// Transaction gas cost
|
||||
localBalanceStore.burnGas(txReceipt.from, DeploymentManager.gasPrice.times(txReceipt.gasUsed));
|
||||
|
||||
// Taker -> Maker
|
||||
localBalanceStore.transferAsset(taker.address, maker.address, order.takerAssetAmount, order.takerAssetData);
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
taker.address,
|
||||
maker.address,
|
||||
order.takerAssetAmount,
|
||||
order.takerAssetData,
|
||||
);
|
||||
// Maker -> Taker
|
||||
localBalanceStore.transferAsset(maker.address, taker.address, order.makerAssetAmount, order.makerAssetData);
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
maker.address,
|
||||
taker.address,
|
||||
order.makerAssetAmount,
|
||||
order.makerAssetData,
|
||||
);
|
||||
|
||||
// Protocol fee
|
||||
if (remainingValue.isGreaterThanOrEqualTo(DeploymentManager.protocolFee)) {
|
||||
@@ -117,11 +129,11 @@ blockchainTests.resets('fillOrder integration tests', env => {
|
||||
);
|
||||
remainingValue = remainingValue.minus(DeploymentManager.protocolFee);
|
||||
} else {
|
||||
localBalanceStore.transferAsset(
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
taker.address,
|
||||
deployment.staking.stakingProxy.address,
|
||||
DeploymentManager.protocolFee,
|
||||
assetDataUtils.encodeERC20AssetData(deployment.tokens.weth.address),
|
||||
await devUtils.encodeERC20AssetData.callAsync(deployment.tokens.weth.address),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -178,7 +190,7 @@ blockchainTests.resets('fillOrder integration tests', env => {
|
||||
const receipt = await taker.fillOrderAsync(order, order.takerAssetAmount);
|
||||
|
||||
// Check balances
|
||||
const expectedBalances = simulateFill(order, receipt);
|
||||
const expectedBalances = await simulateFillAsync(order, receipt);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
|
||||
@@ -204,7 +216,7 @@ blockchainTests.resets('fillOrder integration tests', env => {
|
||||
const receipt = await taker.fillOrderAsync(order, order.takerAssetAmount);
|
||||
|
||||
// Check balances
|
||||
const expectedBalances = simulateFill(order, receipt);
|
||||
const expectedBalances = await simulateFillAsync(order, receipt);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
|
||||
@@ -237,7 +249,7 @@ blockchainTests.resets('fillOrder integration tests', env => {
|
||||
|
||||
// Fetch the current balances
|
||||
await balanceStore.updateBalancesAsync();
|
||||
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||
const expectedBalances = LocalBalanceStore.create(devUtils, balanceStore);
|
||||
|
||||
// End the epoch. This should wrap the staking proxy's ETH balance.
|
||||
const endEpochReceipt = await delegator.endEpochAsync();
|
||||
@@ -279,11 +291,11 @@ blockchainTests.resets('fillOrder integration tests', env => {
|
||||
const [finalizePoolReceipt] = await delegator.finalizePoolsAsync([poolId]);
|
||||
|
||||
// Check balances
|
||||
expectedBalances.transferAsset(
|
||||
await expectedBalances.transferAssetAsync(
|
||||
deployment.staking.stakingProxy.address,
|
||||
operator.address,
|
||||
operatorReward,
|
||||
assetDataUtils.encodeERC20AssetData(deployment.tokens.weth.address),
|
||||
await devUtils.encodeERC20AssetData.callAsync(deployment.tokens.weth.address),
|
||||
);
|
||||
expectedBalances.burnGas(delegator.address, DeploymentManager.gasPrice.times(finalizePoolReceipt.gasUsed));
|
||||
await balanceStore.updateBalancesAsync();
|
||||
@@ -340,7 +352,7 @@ blockchainTests.resets('fillOrder integration tests', env => {
|
||||
const order = await maker.signOrderAsync();
|
||||
const receipt = await taker.fillOrderAsync(order, order.takerAssetAmount, { value: constants.ZERO_AMOUNT });
|
||||
const rewardsAvailable = DeploymentManager.protocolFee;
|
||||
const expectedBalances = simulateFill(order, receipt, constants.ZERO_AMOUNT);
|
||||
const expectedBalances = await simulateFillAsync(order, receipt, constants.ZERO_AMOUNT);
|
||||
|
||||
// End the epoch. This should wrap the staking proxy's ETH balance.
|
||||
const endEpochReceipt = await delegator.endEpochAsync();
|
||||
@@ -359,11 +371,11 @@ blockchainTests.resets('fillOrder integration tests', env => {
|
||||
const [finalizePoolReceipt] = await delegator.finalizePoolsAsync([poolId]);
|
||||
|
||||
// Check balances
|
||||
expectedBalances.transferAsset(
|
||||
await expectedBalances.transferAssetAsync(
|
||||
deployment.staking.stakingProxy.address,
|
||||
operator.address,
|
||||
operatorReward,
|
||||
assetDataUtils.encodeERC20AssetData(deployment.tokens.weth.address),
|
||||
await devUtils.encodeERC20AssetData.callAsync(deployment.tokens.weth.address),
|
||||
);
|
||||
expectedBalances.burnGas(delegator.address, DeploymentManager.gasPrice.times(finalizePoolReceipt.gasUsed));
|
||||
await balanceStore.updateBalancesAsync();
|
||||
|
||||
Reference in New Issue
Block a user