import { ERC1155MintableContract, ERC1155TransferSingleEventArgs } from '@0x/contracts-erc1155'; import { DummyERC20TokenContract, WETH9Contract } from '@0x/contracts-erc20'; import { DummyERC721TokenContract } from '@0x/contracts-erc721'; import { constants, filterLogsToArguments, getRandomInteger, TransactionFactory } from '@0x/contracts-test-utils'; import { SignatureType, SignedZeroExTransaction, ZeroExTransaction } from '@0x/types'; import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import { AssertionResult } from '../assertions/function_assertion'; import { DeploymentManager } from '../deployment_manager'; import { SimulationEnvironment } from '../simulation'; export type Constructor = new (...args: any[]) => T; export interface ActorConfig { name?: string; deployment: DeploymentManager; simulationEnvironment?: SimulationEnvironment; [mixinProperty: string]: any; } export class Actor { public static count: number = 0; public readonly address: string; public readonly name: string; public readonly privateKey: Buffer; public readonly deployment: DeploymentManager; public readonly simulationEnvironment?: SimulationEnvironment; public simulationActions: { [action: string]: AsyncIterableIterator; } = {}; protected readonly _transactionFactory: TransactionFactory; constructor(config: ActorConfig) { Actor.count++; // Emit an error if the actor count is too high. if (Actor.count >= config.deployment.accounts.length) { throw new Error('Actor count too large'); } this.address = config.deployment.accounts[Actor.count]; this.name = config.name || this.address; this.deployment = config.deployment; this.privateKey = constants.TESTRPC_PRIVATE_KEYS[config.deployment.accounts.indexOf(this.address)]; this.simulationEnvironment = config.simulationEnvironment; this._transactionFactory = new TransactionFactory( this.privateKey, config.deployment.exchange.address, config.deployment.chainId, ); } /** * Sets a balance for an ERC20 token and approves a spender (defaults to the ERC20 asset proxy) * to transfer the token. */ public async configureERC20TokenAsync( token: DummyERC20TokenContract | WETH9Contract, spender?: string, amount?: BigNumber, ): Promise { if (token instanceof DummyERC20TokenContract) { await token .setBalance(this.address, amount || constants.INITIAL_ERC20_BALANCE) .awaitTransactionSuccessAsync(); } else { await token.deposit().awaitTransactionSuccessAsync({ from: this.address, value: amount || constants.ONE_ETHER, }); } await token .approve(spender || this.deployment.assetProxies.erc20Proxy.address, constants.MAX_UINT256) .awaitTransactionSuccessAsync({ from: this.address }); } /** * Mints some number of ERC721 NFTs and approves a spender (defaults to the ERC721 asset proxy) * to transfer the token. */ public async configureERC721TokenAsync( token: DummyERC721TokenContract, spender?: string, numToMint: number = 1, ): Promise { const tokenIds: BigNumber[] = []; _.times(numToMint, async () => { const tokenId = getRandomInteger(constants.ZERO_AMOUNT, constants.MAX_UINT256); await token.mint(this.address, tokenId).awaitTransactionSuccessAsync({ from: this.address, }); tokenIds.push(tokenId); }); await token .setApprovalForAll(spender || this.deployment.assetProxies.erc721Proxy.address, true) .awaitTransactionSuccessAsync({ from: this.address, }); return tokenIds; } /** * Mints some number of ERC1115 fungible tokens and approves a spender (defaults to the ERC1155 asset proxy) * to transfer the token. */ public async configureERC1155TokenAsync( token: ERC1155MintableContract, spender?: string, amount?: BigNumber, ): Promise { // Create a fungible token. const receipt = await token.create('', false).awaitTransactionSuccessAsync({ from: this.address }); const logs = filterLogsToArguments(receipt.logs, 'TransferSingle'); // Throw if the wrong number of logs were received. if (logs.length !== 1) { throw new Error('Invalid number of `TransferSingle` logs'); } const { id } = logs[0]; // Mint the token await token .mintFungible(id, [this.address], [amount || new BigNumber(constants.NUM_ERC1155_FUNGIBLE_TOKENS_MINT)]) .awaitTransactionSuccessAsync({ from: this.address }); // Set approval for all token types for the spender. await token .setApprovalForAll(spender || this.deployment.assetProxies.erc1155Proxy.address, true) .awaitTransactionSuccessAsync({ from: this.address }); return id; } /** * Signs a transaction. */ public async signTransactionAsync( customTransactionParams: Partial, signatureType: SignatureType = SignatureType.EthSign, ): Promise { return this._transactionFactory.newSignedTransactionAsync(customTransactionParams, signatureType); } }