300 lines
12 KiB
TypeScript
300 lines
12 KiB
TypeScript
import { DummyERC20TokenContract, DummyERC721TokenContract, ExchangeContract } from '@0x/abi-gen-wrappers';
|
|
import { assetDataUtils } from '@0x/order-utils';
|
|
import { orderFactory } from '@0x/order-utils/lib/src/order_factory';
|
|
import { OrderWithoutDomain, SignedOrder } from '@0x/types';
|
|
import { BigNumber } from '@0x/utils';
|
|
import { Web3Wrapper } from '@0x/web3-wrapper';
|
|
import { SupportedProvider } from 'ethereum-types';
|
|
import * as _ from 'lodash';
|
|
|
|
import { constants } from './constants';
|
|
|
|
export class FillScenarios {
|
|
private readonly _web3Wrapper: Web3Wrapper;
|
|
private readonly _userAddresses: string[];
|
|
private readonly _coinbase: string;
|
|
private readonly _exchangeAddress: string;
|
|
private readonly _erc20ProxyAddress: string;
|
|
private readonly _erc721ProxyAddress: string;
|
|
constructor(
|
|
supportedProvider: SupportedProvider,
|
|
userAddresses: string[],
|
|
exchangeAddress: string,
|
|
erc20ProxyAddress: string,
|
|
erc721ProxyAddress: string,
|
|
) {
|
|
this._web3Wrapper = new Web3Wrapper(supportedProvider);
|
|
this._userAddresses = userAddresses;
|
|
this._coinbase = userAddresses[0];
|
|
this._exchangeAddress = exchangeAddress;
|
|
this._erc20ProxyAddress = erc20ProxyAddress;
|
|
this._erc721ProxyAddress = erc721ProxyAddress;
|
|
}
|
|
public async createFillableSignedOrderAsync(
|
|
makerAssetData: string,
|
|
takerAssetData: string,
|
|
makerAddress: string,
|
|
takerAddress: string,
|
|
fillableAmount: BigNumber,
|
|
expirationTimeSeconds?: BigNumber,
|
|
): Promise<SignedOrder> {
|
|
return this.createAsymmetricFillableSignedOrderAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
fillableAmount,
|
|
expirationTimeSeconds,
|
|
);
|
|
}
|
|
public async createFillableSignedOrderWithFeesAsync(
|
|
makerAssetData: string,
|
|
takerAssetData: string,
|
|
makerFeeAssetData: string,
|
|
takerFeeAssetData: string,
|
|
makerFee: BigNumber,
|
|
takerFee: BigNumber,
|
|
makerAddress: string,
|
|
takerAddress: string,
|
|
fillableAmount: BigNumber,
|
|
feeRecipientAddress: string,
|
|
expirationTimeSeconds?: BigNumber,
|
|
senderAddress?: string,
|
|
): Promise<SignedOrder> {
|
|
return this._createAsymmetricFillableSignedOrderWithFeesAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerFeeAssetData,
|
|
takerFeeAssetData,
|
|
makerFee,
|
|
takerFee,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
fillableAmount,
|
|
feeRecipientAddress,
|
|
expirationTimeSeconds,
|
|
senderAddress,
|
|
);
|
|
}
|
|
public async createAsymmetricFillableSignedOrderAsync(
|
|
makerAssetData: string,
|
|
takerAssetData: string,
|
|
makerAddress: string,
|
|
takerAddress: string,
|
|
makerFillableAmount: BigNumber,
|
|
takerFillableAmount: BigNumber,
|
|
expirationTimeSeconds?: BigNumber,
|
|
): Promise<SignedOrder> {
|
|
const makerFeeAssetData = constants.NULL_BYTES;
|
|
const takerFeeAssetData = constants.NULL_BYTES;
|
|
const makerFee = new BigNumber(0);
|
|
const takerFee = new BigNumber(0);
|
|
const feeRecipientAddress = constants.NULL_ADDRESS;
|
|
return this._createAsymmetricFillableSignedOrderWithFeesAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerFeeAssetData,
|
|
takerFeeAssetData,
|
|
makerFee,
|
|
takerFee,
|
|
makerAddress,
|
|
takerAddress,
|
|
makerFillableAmount,
|
|
takerFillableAmount,
|
|
feeRecipientAddress,
|
|
expirationTimeSeconds,
|
|
);
|
|
}
|
|
public async createPartiallyFilledSignedOrderAsync(
|
|
makerAssetData: string,
|
|
takerAssetData: string,
|
|
takerAddress: string,
|
|
fillableAmount: BigNumber,
|
|
partialFillAmount: BigNumber,
|
|
): Promise<SignedOrder> {
|
|
const [makerAddress] = this._userAddresses;
|
|
const signedOrder = await this.createAsymmetricFillableSignedOrderAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
fillableAmount,
|
|
);
|
|
const exchangeInstance = new ExchangeContract(
|
|
signedOrder.domain.verifyingContractAddress,
|
|
this._web3Wrapper.getProvider(),
|
|
this._web3Wrapper.getContractDefaults(),
|
|
);
|
|
|
|
const orderWithoutDomain = _.omit(signedOrder, ['signature', 'domain']) as OrderWithoutDomain;
|
|
|
|
const txHash = await exchangeInstance.fillOrder.sendTransactionAsync(
|
|
orderWithoutDomain,
|
|
partialFillAmount,
|
|
signedOrder.signature,
|
|
{ from: takerAddress },
|
|
);
|
|
await this._web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
|
|
return signedOrder;
|
|
}
|
|
private async _createAsymmetricFillableSignedOrderWithFeesAsync(
|
|
makerAssetData: string,
|
|
takerAssetData: string,
|
|
makerFeeAssetData: string,
|
|
takerFeeAssetData: string,
|
|
makerFee: BigNumber,
|
|
takerFee: BigNumber,
|
|
makerAddress: string,
|
|
takerAddress: string,
|
|
makerFillableAmount: BigNumber,
|
|
takerFillableAmount: BigNumber,
|
|
feeRecipientAddress: string,
|
|
expirationTimeSeconds?: BigNumber,
|
|
senderAddress?: string,
|
|
): Promise<SignedOrder> {
|
|
await this._increaseBalanceAndAllowanceWithAssetDataAsync(makerAssetData, makerAddress, makerFillableAmount);
|
|
await this._increaseBalanceAndAllowanceWithAssetDataAsync(takerAssetData, takerAddress, takerFillableAmount);
|
|
// Fees
|
|
await Promise.all([
|
|
this._increaseERC20BalanceAndAllowanceAsync(makerFeeAssetData, makerAddress, makerFee),
|
|
this._increaseERC20BalanceAndAllowanceAsync(takerFeeAssetData, takerAddress, takerFee),
|
|
]);
|
|
const _senderAddress = senderAddress ? senderAddress : constants.NULL_ADDRESS;
|
|
|
|
const signedOrder = await orderFactory.createSignedOrderAsync(
|
|
this._web3Wrapper.getProvider(),
|
|
makerAddress,
|
|
makerFillableAmount,
|
|
makerAssetData,
|
|
takerFillableAmount,
|
|
takerAssetData,
|
|
this._exchangeAddress,
|
|
{
|
|
takerAddress,
|
|
senderAddress: _senderAddress,
|
|
makerFeeAssetData,
|
|
takerFeeAssetData,
|
|
makerFee,
|
|
takerFee,
|
|
feeRecipientAddress,
|
|
expirationTimeSeconds,
|
|
},
|
|
);
|
|
return signedOrder;
|
|
}
|
|
private async _increaseERC721BalanceAndAllowanceAsync(
|
|
tokenAddress: string,
|
|
address: string,
|
|
tokenId: BigNumber,
|
|
): Promise<void> {
|
|
await this._increaseERC721BalanceAsync(tokenAddress, address, tokenId);
|
|
await this._increaseERC721AllowanceAsync(tokenAddress, address, tokenId);
|
|
}
|
|
private async _increaseERC721AllowanceAsync(
|
|
tokenAddress: string,
|
|
address: string,
|
|
tokenId: BigNumber,
|
|
): Promise<void> {
|
|
const erc721Token = new DummyERC721TokenContract(
|
|
tokenAddress,
|
|
this._web3Wrapper.getProvider(),
|
|
this._web3Wrapper.getContractDefaults(),
|
|
);
|
|
const txHash = await erc721Token.approve.sendTransactionAsync(this._erc721ProxyAddress, tokenId, {
|
|
from: address,
|
|
});
|
|
await this._web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
|
|
}
|
|
private async _increaseERC721BalanceAsync(
|
|
tokenAddress: string,
|
|
address: string,
|
|
tokenId: BigNumber,
|
|
): Promise<void> {
|
|
const erc721Token = new DummyERC721TokenContract(
|
|
tokenAddress,
|
|
this._web3Wrapper.getProvider(),
|
|
this._web3Wrapper.getContractDefaults(),
|
|
);
|
|
try {
|
|
const currentOwner = await erc721Token.ownerOf.callAsync(tokenId);
|
|
if (currentOwner !== address) {
|
|
throw new Error(`Token ${tokenAddress}:${tokenId} is already owner by ${currentOwner}`);
|
|
}
|
|
} catch (err) {
|
|
const txHash = await erc721Token.mint.sendTransactionAsync(address, tokenId, { from: this._coinbase });
|
|
await this._web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
|
|
}
|
|
}
|
|
private async _increaseERC20BalanceAndAllowanceAsync(
|
|
tokenAddress: string,
|
|
address: string,
|
|
amount: BigNumber,
|
|
): Promise<void> {
|
|
if (amount.isZero() || address === constants.NULL_ADDRESS) {
|
|
return; // noop
|
|
}
|
|
await Promise.all([
|
|
this._increaseERC20BalanceAsync(tokenAddress, address, amount),
|
|
this._increaseERC20AllowanceAsync(tokenAddress, address, amount),
|
|
]);
|
|
}
|
|
private async _increaseERC20BalanceAsync(tokenAddress: string, address: string, amount: BigNumber): Promise<void> {
|
|
const erc20Token = new DummyERC20TokenContract(
|
|
tokenAddress,
|
|
this._web3Wrapper.getProvider(),
|
|
this._web3Wrapper.getContractDefaults(),
|
|
);
|
|
const txHash = await erc20Token.transfer.sendTransactionAsync(address, amount, {
|
|
from: this._coinbase,
|
|
});
|
|
await this._web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
|
|
}
|
|
private async _increaseERC20AllowanceAsync(
|
|
tokenAddress: string,
|
|
address: string,
|
|
amount: BigNumber,
|
|
): Promise<void> {
|
|
const erc20Token = new DummyERC20TokenContract(
|
|
tokenAddress,
|
|
this._web3Wrapper.getProvider(),
|
|
this._web3Wrapper.getContractDefaults(),
|
|
);
|
|
const oldMakerAllowance = await erc20Token.allowance.callAsync(address, this._erc20ProxyAddress);
|
|
const newMakerAllowance = oldMakerAllowance.plus(amount);
|
|
|
|
const txHash = await erc20Token.approve.sendTransactionAsync(this._erc20ProxyAddress, newMakerAllowance, {
|
|
from: address,
|
|
});
|
|
await this._web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
|
|
}
|
|
private async _increaseBalanceAndAllowanceWithAssetDataAsync(
|
|
assetData: string,
|
|
userAddress: string,
|
|
amount: BigNumber,
|
|
): Promise<void> {
|
|
const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData);
|
|
if (assetDataUtils.isERC20AssetData(decodedAssetData)) {
|
|
await this._increaseERC20BalanceAndAllowanceAsync(decodedAssetData.tokenAddress, userAddress, amount);
|
|
} else if (assetDataUtils.isERC721AssetData(decodedAssetData)) {
|
|
await this._increaseERC721BalanceAndAllowanceAsync(
|
|
decodedAssetData.tokenAddress,
|
|
userAddress,
|
|
decodedAssetData.tokenId,
|
|
);
|
|
} else if (assetDataUtils.isMultiAssetData(decodedAssetData)) {
|
|
for (const [index, nestedAssetDataElement] of decodedAssetData.nestedAssetData.entries()) {
|
|
const amountsElement = decodedAssetData.amounts[index];
|
|
const totalAmount = amount.times(amountsElement);
|
|
await this._increaseBalanceAndAllowanceWithAssetDataAsync(
|
|
nestedAssetDataElement,
|
|
userAddress,
|
|
totalAmount,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|