diff --git a/packages/contract-artifacts/CHANGELOG.json b/packages/contract-artifacts/CHANGELOG.json index fa3d6e3aa9..18105a0e6d 100644 --- a/packages/contract-artifacts/CHANGELOG.json +++ b/packages/contract-artifacts/CHANGELOG.json @@ -29,6 +29,10 @@ { "note": "Added `ERC20BridgeSampler.sampleSellsFromMultiBridge", "pr": 2593 + }, + { + "note": "Added `ITransformERC20`", + "pr": 2591 } ] }, diff --git a/packages/contract-artifacts/artifacts/ITransformERC20.json b/packages/contract-artifacts/artifacts/ITransformERC20.json new file mode 100644 index 0000000000..ba0516b718 --- /dev/null +++ b/packages/contract-artifacts/artifacts/ITransformERC20.json @@ -0,0 +1,152 @@ +{ + "schemaVersion": "2.0.0", + "contractName": "ITransformERC20", + "compilerOutput": { + "abi": [ + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "taker", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "inputToken", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "outputToken", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "inputTokenAmount", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "outputTokenAmount", "type": "uint256" } + ], + "name": "TransformedERC20", + "type": "event" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "callDataHash", "type": "bytes32" }, + { "internalType": "address payable", "name": "taker", "type": "address" }, + { "internalType": "contract IERC20TokenV06", "name": "inputToken", "type": "address" }, + { "internalType": "contract IERC20TokenV06", "name": "outputToken", "type": "address" }, + { "internalType": "uint256", "name": "inputTokenAmount", "type": "uint256" }, + { "internalType": "uint256", "name": "minOutputTokenAmount", "type": "uint256" }, + { + "components": [ + { "internalType": "contract IERC20Transformer", "name": "transformer", "type": "address" }, + { "internalType": "bytes", "name": "data", "type": "bytes" } + ], + "internalType": "struct ITransformERC20.Transformation[]", + "name": "transformations", + "type": "tuple[]" + } + ], + "name": "_transformERC20", + "outputs": [{ "internalType": "uint256", "name": "outputTokenAmount", "type": "uint256" }], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "createTransformWallet", + "outputs": [{ "internalType": "contract IFlashWallet", "name": "wallet", "type": "address" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getTransformWallet", + "outputs": [{ "internalType": "contract IFlashWallet", "name": "wallet", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTransformerDeployer", + "outputs": [{ "internalType": "address", "name": "deployer", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "contract IERC20TokenV06", "name": "inputToken", "type": "address" }, + { "internalType": "contract IERC20TokenV06", "name": "outputToken", "type": "address" }, + { "internalType": "uint256", "name": "inputTokenAmount", "type": "uint256" }, + { "internalType": "uint256", "name": "minOutputTokenAmount", "type": "uint256" }, + { + "components": [ + { "internalType": "contract IERC20Transformer", "name": "transformer", "type": "address" }, + { "internalType": "bytes", "name": "data", "type": "bytes" } + ], + "internalType": "struct ITransformERC20.Transformation[]", + "name": "transformations", + "type": "tuple[]" + } + ], + "name": "transformERC20", + "outputs": [{ "internalType": "uint256", "name": "outputTokenAmount", "type": "uint256" }], + "stateMutability": "payable", + "type": "function" + } + ], + "devdoc": { + "details": "Feature to composably transform between ERC20 tokens.", + "methods": { + "_transformERC20(bytes32,address,address,address,uint256,uint256,(address,bytes)[])": { + "details": "Internal version of `transformERC20()`. Only callable from within.", + "params": { + "callDataHash": "Hash of the ingress calldata.", + "inputToken": "The token being provided by the taker. If `0xeee...`, ETH is implied and should be provided with the call.`", + "inputTokenAmount": "The amount of `inputToken` to take from the taker.", + "minOutputTokenAmount": "The minimum amount of `outputToken` the taker must receive for the entire transformation to succeed.", + "outputToken": "The token to be acquired by the taker. `0xeee...` implies ETH.", + "taker": "The taker address.", + "transformations": "The transformations to execute on the token balance(s) in sequence." + }, + "returns": { "outputTokenAmount": "The amount of `outputToken` received by the taker." } + }, + "createTransformWallet()": { + "details": "Deploy a new flash wallet instance and replace the current one with it. Useful if we somehow break the current wallet instance. Anyone can call this.", + "returns": { "wallet": "The new wallet instance." } + }, + "getTransformWallet()": { + "details": "Return the current wallet instance that will serve as the execution context for transformations.", + "returns": { "wallet": "The wallet instance." } + }, + "getTransformerDeployer()": { + "details": "Return the allowed deployer for transformers.", + "returns": { "deployer": "The transform deployer address." } + }, + "transformERC20(address,address,uint256,uint256,(address,bytes)[])": { + "details": "Executes a series of transformations to convert an ERC20 `inputToken` to an ERC20 `outputToken`.", + "params": { + "inputToken": "The token being provided by the sender. If `0xeee...`, ETH is implied and should be provided with the call.`", + "inputTokenAmount": "The amount of `inputToken` to take from the sender.", + "minOutputTokenAmount": "The minimum amount of `outputToken` the sender must receive for the entire transformation to succeed.", + "outputToken": "The token to be acquired by the sender. `0xeee...` implies ETH.", + "transformations": "The transformations to execute on the token balance(s) in sequence." + }, + "returns": { "outputTokenAmount": "The amount of `outputToken` received by the sender." } + } + } + }, + "evm": { "bytecode": { "object": "0x" }, "deployedBytecode": { "immutableReferences": {}, "object": "0x" } } + }, + "compiler": { + "name": "solc", + "version": "0.6.8+commit.0bbfe453", + "settings": { + "optimizer": { + "enabled": true, + "runs": 1000000, + "details": { "yul": true, "deduplicate": true, "cse": true, "constantOptimizer": true } + }, + "outputSelection": { + "*": { + "*": [ + "abi", + "devdoc", + "evm.bytecode.object", + "evm.bytecode.sourceMap", + "evm.deployedBytecode.object", + "evm.deployedBytecode.sourceMap" + ] + } + }, + "evmVersion": "istanbul" + } + }, + "chains": {} +} diff --git a/packages/contract-artifacts/src/index.ts b/packages/contract-artifacts/src/index.ts index 78d92a62ba..e51c6ff302 100644 --- a/packages/contract-artifacts/src/index.ts +++ b/packages/contract-artifacts/src/index.ts @@ -26,6 +26,7 @@ import * as ZRXToken from '../artifacts/ZRXToken.json'; import * as ERC20BridgeProxy from '../artifacts/ERC20BridgeProxy.json'; import * as ZrxVault from '../artifacts/ZrxVault.json'; import * as IERC20BridgeSampler from '../artifacts/IERC20BridgeSampler.json'; +import * as ITransformERC20 from '../artifacts/ITransformERC20.json'; export { AssetProxyOwner, @@ -56,4 +57,5 @@ export { StakingProxy, ZrxVault, IERC20BridgeSampler, + ITransformERC20, }; diff --git a/packages/contract-artifacts/tsconfig.json b/packages/contract-artifacts/tsconfig.json index 1c174c6d3b..7f6106b0a3 100644 --- a/packages/contract-artifacts/tsconfig.json +++ b/packages/contract-artifacts/tsconfig.json @@ -36,6 +36,7 @@ "./artifacts/StakingProxy.json", "./artifacts/ZrxVault.json", "./artifacts/ERC20BridgeProxy.json", - "./artifacts/IERC20BridgeSampler.json" + "./artifacts/IERC20BridgeSampler.json", + "./artifacts/ITransformERC20.json" ] } diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index 5b703ba9fa..eff6909ab8 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -33,6 +33,10 @@ { "note": "Added `ERC20BridgeSampler.sampleSellsFromMultiBridge", "pr": 2593 + }, + { + "note": "Add `ITransformERC20`", + "pr": "TODO" } ] }, diff --git a/packages/contract-wrappers/package.json b/packages/contract-wrappers/package.json index bd73ccb222..b5e220c31d 100644 --- a/packages/contract-wrappers/package.json +++ b/packages/contract-wrappers/package.json @@ -32,7 +32,7 @@ "wrappers:generate": "abi-gen --abis ${npm_package_config_abis} --output src/generated-wrappers --backend ethers" }, "config": { - "abis": "../contract-artifacts/artifacts/@(DevUtils|ERC20Token|ERC721Token|Exchange|Forwarder|IAssetData|LibTransactionDecoder|WETH9|Coordinator|Staking|StakingProxy|IERC20BridgeSampler|ERC20BridgeSampler|GodsUnchainedValidator|Broker|ILiquidityProvider|ILiquidityProviderRegistry|MaximumGasPrice).json" + "abis": "../contract-artifacts/artifacts/@(DevUtils|ERC20Token|ERC721Token|Exchange|Forwarder|IAssetData|LibTransactionDecoder|WETH9|Coordinator|Staking|StakingProxy|IERC20BridgeSampler|ERC20BridgeSampler|GodsUnchainedValidator|Broker|ILiquidityProvider|ILiquidityProviderRegistry|MaximumGasPrice|ITransformERC20).json" }, "gitpkg": { "registry": "git@github.com:0xProject/gitpkg-registry.git" diff --git a/packages/contract-wrappers/src/generated-wrappers/i_transform_erc20.ts b/packages/contract-wrappers/src/generated-wrappers/i_transform_erc20.ts new file mode 100644 index 0000000000..47b87701d1 --- /dev/null +++ b/packages/contract-wrappers/src/generated-wrappers/i_transform_erc20.ts @@ -0,0 +1,825 @@ +// tslint:disable:no-consecutive-blank-lines ordered-imports align trailing-comma enum-naming +// tslint:disable:whitespace no-unbound-method no-trailing-whitespace +// tslint:disable:no-unused-variable +import { + AwaitTransactionSuccessOpts, + ContractFunctionObj, + ContractTxFunctionObj, + SendTransactionOpts, + BaseContract, + SubscriptionManager, + PromiseWithTransactionHash, + methodAbiToFunctionSignature, + linkLibrariesInBytecode, +} from '@0x/base-contract'; +import { schemas } from '@0x/json-schemas'; +import { + BlockParam, + BlockParamLiteral, + BlockRange, + CallData, + ContractAbi, + ContractArtifact, + DecodedLogArgs, + LogWithDecodedArgs, + MethodAbi, + TransactionReceiptWithDecodedLogs, + TxData, + TxDataPayable, + SupportedProvider, +} from 'ethereum-types'; +import { BigNumber, classUtils, hexUtils, logUtils, providerUtils } from '@0x/utils'; +import { EventCallback, IndexedFilterValues, SimpleContractArtifact } from '@0x/types'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import { assert } from '@0x/assert'; +import * as ethers from 'ethers'; +// tslint:enable:no-unused-variable + +export type ITransformERC20EventArgs = ITransformERC20TransformedERC20EventArgs; + +export enum ITransformERC20Events { + TransformedERC20 = 'TransformedERC20', +} + +export interface ITransformERC20TransformedERC20EventArgs extends DecodedLogArgs { + taker: string; + inputToken: string; + outputToken: string; + inputTokenAmount: BigNumber; + outputTokenAmount: BigNumber; +} + +/* istanbul ignore next */ +// tslint:disable:array-type +// tslint:disable:no-parameter-reassignment +// tslint:disable-next-line:class-name +export class ITransformERC20Contract extends BaseContract { + /** + * @ignore + */ + public static deployedBytecode: string | undefined; + public static contractName = 'ITransformERC20'; + private readonly _methodABIIndex: { [name: string]: number } = {}; + private readonly _subscriptionManager: SubscriptionManager; + public static async deployFrom0xArtifactAsync( + artifact: ContractArtifact | SimpleContractArtifact, + supportedProvider: SupportedProvider, + txDefaults: Partial, + logDecodeDependencies: { [contractName: string]: ContractArtifact | SimpleContractArtifact }, + ): Promise { + assert.doesConformToSchema('txDefaults', txDefaults, schemas.txDataSchema, [ + schemas.addressSchema, + schemas.numberSchema, + schemas.jsNumber, + ]); + if (artifact.compilerOutput === undefined) { + throw new Error('Compiler output not found in the artifact file'); + } + const provider = providerUtils.standardizeOrThrow(supportedProvider); + const bytecode = artifact.compilerOutput.evm.bytecode.object; + const abi = artifact.compilerOutput.abi; + const logDecodeDependenciesAbiOnly: { [contractName: string]: ContractAbi } = {}; + if (Object.keys(logDecodeDependencies) !== undefined) { + for (const key of Object.keys(logDecodeDependencies)) { + logDecodeDependenciesAbiOnly[key] = logDecodeDependencies[key].compilerOutput.abi; + } + } + return ITransformERC20Contract.deployAsync(bytecode, abi, provider, txDefaults, logDecodeDependenciesAbiOnly); + } + + public static async deployWithLibrariesFrom0xArtifactAsync( + artifact: ContractArtifact, + libraryArtifacts: { [libraryName: string]: ContractArtifact }, + supportedProvider: SupportedProvider, + txDefaults: Partial, + logDecodeDependencies: { [contractName: string]: ContractArtifact | SimpleContractArtifact }, + ): Promise { + assert.doesConformToSchema('txDefaults', txDefaults, schemas.txDataSchema, [ + schemas.addressSchema, + schemas.numberSchema, + schemas.jsNumber, + ]); + if (artifact.compilerOutput === undefined) { + throw new Error('Compiler output not found in the artifact file'); + } + const provider = providerUtils.standardizeOrThrow(supportedProvider); + const abi = artifact.compilerOutput.abi; + const logDecodeDependenciesAbiOnly: { [contractName: string]: ContractAbi } = {}; + if (Object.keys(logDecodeDependencies) !== undefined) { + for (const key of Object.keys(logDecodeDependencies)) { + logDecodeDependenciesAbiOnly[key] = logDecodeDependencies[key].compilerOutput.abi; + } + } + const libraryAddresses = await ITransformERC20Contract._deployLibrariesAsync( + artifact, + libraryArtifacts, + new Web3Wrapper(provider), + txDefaults, + ); + const bytecode = linkLibrariesInBytecode(artifact, libraryAddresses); + return ITransformERC20Contract.deployAsync(bytecode, abi, provider, txDefaults, logDecodeDependenciesAbiOnly); + } + + public static async deployAsync( + bytecode: string, + abi: ContractAbi, + supportedProvider: SupportedProvider, + txDefaults: Partial, + logDecodeDependencies: { [contractName: string]: ContractAbi }, + ): Promise { + assert.isHexString('bytecode', bytecode); + assert.doesConformToSchema('txDefaults', txDefaults, schemas.txDataSchema, [ + schemas.addressSchema, + schemas.numberSchema, + schemas.jsNumber, + ]); + const provider = providerUtils.standardizeOrThrow(supportedProvider); + const constructorAbi = BaseContract._lookupConstructorAbi(abi); + [] = BaseContract._formatABIDataItemList(constructorAbi.inputs, [], BaseContract._bigNumberToString); + const iface = new ethers.utils.Interface(abi); + const deployInfo = iface.deployFunction; + const txData = deployInfo.encode(bytecode, []); + const web3Wrapper = new Web3Wrapper(provider); + const txDataWithDefaults = await BaseContract._applyDefaultsToContractTxDataAsync( + { + data: txData, + ...txDefaults, + }, + web3Wrapper.estimateGasAsync.bind(web3Wrapper), + ); + const txHash = await web3Wrapper.sendTransactionAsync(txDataWithDefaults); + logUtils.log(`transactionHash: ${txHash}`); + const txReceipt = await web3Wrapper.awaitTransactionSuccessAsync(txHash); + logUtils.log(`ITransformERC20 successfully deployed at ${txReceipt.contractAddress}`); + const contractInstance = new ITransformERC20Contract( + txReceipt.contractAddress as string, + provider, + txDefaults, + logDecodeDependencies, + ); + contractInstance.constructorArgs = []; + return contractInstance; + } + + /** + * @returns The contract ABI + */ + public static ABI(): ContractAbi { + const abi = [ + { + anonymous: false, + inputs: [ + { + name: 'taker', + type: 'address', + indexed: true, + }, + { + name: 'inputToken', + type: 'address', + indexed: false, + }, + { + name: 'outputToken', + type: 'address', + indexed: false, + }, + { + name: 'inputTokenAmount', + type: 'uint256', + indexed: false, + }, + { + name: 'outputTokenAmount', + type: 'uint256', + indexed: false, + }, + ], + name: 'TransformedERC20', + outputs: [], + type: 'event', + }, + { + inputs: [ + { + name: 'callDataHash', + type: 'bytes32', + }, + { + name: 'taker', + type: 'address', + }, + { + name: 'inputToken', + type: 'address', + }, + { + name: 'outputToken', + type: 'address', + }, + { + name: 'inputTokenAmount', + type: 'uint256', + }, + { + name: 'minOutputTokenAmount', + type: 'uint256', + }, + { + name: 'transformations', + type: 'tuple[]', + components: [ + { + name: 'transformer', + type: 'address', + }, + { + name: 'data', + type: 'bytes', + }, + ], + }, + ], + name: '_transformERC20', + outputs: [ + { + name: 'outputTokenAmount', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'createTransformWallet', + outputs: [ + { + name: 'wallet', + type: 'address', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'getTransformWallet', + outputs: [ + { + name: 'wallet', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTransformerDeployer', + outputs: [ + { + name: 'deployer', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + name: 'inputToken', + type: 'address', + }, + { + name: 'outputToken', + type: 'address', + }, + { + name: 'inputTokenAmount', + type: 'uint256', + }, + { + name: 'minOutputTokenAmount', + type: 'uint256', + }, + { + name: 'transformations', + type: 'tuple[]', + components: [ + { + name: 'transformer', + type: 'address', + }, + { + name: 'data', + type: 'bytes', + }, + ], + }, + ], + name: 'transformERC20', + outputs: [ + { + name: 'outputTokenAmount', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + ] as ContractAbi; + return abi; + } + + protected static async _deployLibrariesAsync( + artifact: ContractArtifact, + libraryArtifacts: { [libraryName: string]: ContractArtifact }, + web3Wrapper: Web3Wrapper, + txDefaults: Partial, + libraryAddresses: { [libraryName: string]: string } = {}, + ): Promise<{ [libraryName: string]: string }> { + const links = artifact.compilerOutput.evm.bytecode.linkReferences; + // Go through all linked libraries, recursively deploying them if necessary. + for (const link of Object.values(links)) { + for (const libraryName of Object.keys(link)) { + if (!libraryAddresses[libraryName]) { + // Library not yet deployed. + const libraryArtifact = libraryArtifacts[libraryName]; + if (!libraryArtifact) { + throw new Error(`Missing artifact for linked library "${libraryName}"`); + } + // Deploy any dependent libraries used by this library. + await ITransformERC20Contract._deployLibrariesAsync( + libraryArtifact, + libraryArtifacts, + web3Wrapper, + txDefaults, + libraryAddresses, + ); + // Deploy this library. + const linkedLibraryBytecode = linkLibrariesInBytecode(libraryArtifact, libraryAddresses); + const txDataWithDefaults = await BaseContract._applyDefaultsToContractTxDataAsync( + { + data: linkedLibraryBytecode, + ...txDefaults, + }, + web3Wrapper.estimateGasAsync.bind(web3Wrapper), + ); + const txHash = await web3Wrapper.sendTransactionAsync(txDataWithDefaults); + logUtils.log(`transactionHash: ${txHash}`); + const { contractAddress } = await web3Wrapper.awaitTransactionSuccessAsync(txHash); + logUtils.log(`${libraryArtifact.contractName} successfully deployed at ${contractAddress}`); + libraryAddresses[libraryArtifact.contractName] = contractAddress as string; + } + } + } + return libraryAddresses; + } + + public getFunctionSignature(methodName: string): string { + const index = this._methodABIIndex[methodName]; + const methodAbi = ITransformERC20Contract.ABI()[index] as MethodAbi; // tslint:disable-line:no-unnecessary-type-assertion + const functionSignature = methodAbiToFunctionSignature(methodAbi); + return functionSignature; + } + + public getABIDecodedTransactionData(methodName: string, callData: string): T { + const functionSignature = this.getFunctionSignature(methodName); + const self = (this as any) as ITransformERC20Contract; + const abiEncoder = self._lookupAbiEncoder(functionSignature); + const abiDecodedCallData = abiEncoder.strictDecode(callData); + return abiDecodedCallData; + } + + public getABIDecodedReturnData(methodName: string, callData: string): T { + const functionSignature = this.getFunctionSignature(methodName); + const self = (this as any) as ITransformERC20Contract; + const abiEncoder = self._lookupAbiEncoder(functionSignature); + const abiDecodedCallData = abiEncoder.strictDecodeReturnValue(callData); + return abiDecodedCallData; + } + + public getSelector(methodName: string): string { + const functionSignature = this.getFunctionSignature(methodName); + const self = (this as any) as ITransformERC20Contract; + const abiEncoder = self._lookupAbiEncoder(functionSignature); + return abiEncoder.getSelector(); + } + + /** + * Internal version of `transformERC20()`. Only callable from within. + * @param callDataHash Hash of the ingress calldata. + * @param taker The taker address. + * @param inputToken The token being provided by the taker. If + * `0xeee...`, ETH is implied and should be provided with the call.` + * @param outputToken The token to be acquired by the taker. `0xeee...` + * implies ETH. + * @param inputTokenAmount The amount of `inputToken` to take from the taker. + * @param minOutputTokenAmount The minimum amount of `outputToken` the taker + * must receive for the entire transformation to succeed. + * @param transformations The transformations to execute on the token + * balance(s) in sequence. + */ + public _transformERC20( + callDataHash: string, + taker: string, + inputToken: string, + outputToken: string, + inputTokenAmount: BigNumber, + minOutputTokenAmount: BigNumber, + transformations: Array<{ transformer: string; data: string }>, + ): ContractTxFunctionObj { + const self = (this as any) as ITransformERC20Contract; + assert.isString('callDataHash', callDataHash); + assert.isString('taker', taker); + assert.isString('inputToken', inputToken); + assert.isString('outputToken', outputToken); + assert.isBigNumber('inputTokenAmount', inputTokenAmount); + assert.isBigNumber('minOutputTokenAmount', minOutputTokenAmount); + assert.isArray('transformations', transformations); + const functionSignature = '_transformERC20(bytes32,address,address,address,uint256,uint256,(address,bytes)[])'; + + return { + async sendTransactionAsync( + txData?: Partial | undefined, + opts: SendTransactionOpts = { shouldValidate: true }, + ): Promise { + const txDataWithDefaults = await self._applyDefaultsToTxDataAsync( + { ...txData, data: this.getABIEncodedTransactionData() }, + this.estimateGasAsync.bind(this), + ); + if (opts.shouldValidate !== false) { + await this.callAsync(txDataWithDefaults); + } + return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults); + }, + awaitTransactionSuccessAsync( + txData?: Partial, + opts: AwaitTransactionSuccessOpts = { shouldValidate: true }, + ): PromiseWithTransactionHash { + return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts); + }, + async estimateGasAsync(txData?: Partial | undefined): Promise { + const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({ + ...txData, + data: this.getABIEncodedTransactionData(), + }); + return self._web3Wrapper.estimateGasAsync(txDataWithDefaults); + }, + async callAsync(callData: Partial = {}, defaultBlock?: BlockParam): Promise { + BaseContract._assertCallParams(callData, defaultBlock); + const rawCallResult = await self._performCallAsync( + { ...callData, data: this.getABIEncodedTransactionData() }, + defaultBlock, + ); + const abiEncoder = self._lookupAbiEncoder(functionSignature); + BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder); + return abiEncoder.strictDecodeReturnValue(rawCallResult); + }, + getABIEncodedTransactionData(): string { + return self._strictEncodeArguments(functionSignature, [ + callDataHash, + taker.toLowerCase(), + inputToken.toLowerCase(), + outputToken.toLowerCase(), + inputTokenAmount, + minOutputTokenAmount, + transformations, + ]); + }, + }; + } + /** + * Deploy a new flash wallet instance and replace the current one with it. + * Useful if we somehow break the current wallet instance. + * Anyone can call this. + */ + public createTransformWallet(): ContractTxFunctionObj { + const self = (this as any) as ITransformERC20Contract; + const functionSignature = 'createTransformWallet()'; + + return { + async sendTransactionAsync( + txData?: Partial | undefined, + opts: SendTransactionOpts = { shouldValidate: true }, + ): Promise { + const txDataWithDefaults = await self._applyDefaultsToTxDataAsync( + { ...txData, data: this.getABIEncodedTransactionData() }, + this.estimateGasAsync.bind(this), + ); + if (opts.shouldValidate !== false) { + await this.callAsync(txDataWithDefaults); + } + return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults); + }, + awaitTransactionSuccessAsync( + txData?: Partial, + opts: AwaitTransactionSuccessOpts = { shouldValidate: true }, + ): PromiseWithTransactionHash { + return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts); + }, + async estimateGasAsync(txData?: Partial | undefined): Promise { + const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({ + ...txData, + data: this.getABIEncodedTransactionData(), + }); + return self._web3Wrapper.estimateGasAsync(txDataWithDefaults); + }, + async callAsync(callData: Partial = {}, defaultBlock?: BlockParam): Promise { + BaseContract._assertCallParams(callData, defaultBlock); + const rawCallResult = await self._performCallAsync( + { ...callData, data: this.getABIEncodedTransactionData() }, + defaultBlock, + ); + const abiEncoder = self._lookupAbiEncoder(functionSignature); + BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder); + return abiEncoder.strictDecodeReturnValue(rawCallResult); + }, + getABIEncodedTransactionData(): string { + return self._strictEncodeArguments(functionSignature, []); + }, + }; + } + /** + * Return the current wallet instance that will serve as the execution + * context for transformations. + */ + public getTransformWallet(): ContractTxFunctionObj { + const self = (this as any) as ITransformERC20Contract; + const functionSignature = 'getTransformWallet()'; + + return { + async sendTransactionAsync( + txData?: Partial | undefined, + opts: SendTransactionOpts = { shouldValidate: true }, + ): Promise { + const txDataWithDefaults = await self._applyDefaultsToTxDataAsync( + { ...txData, data: this.getABIEncodedTransactionData() }, + this.estimateGasAsync.bind(this), + ); + if (opts.shouldValidate !== false) { + await this.callAsync(txDataWithDefaults); + } + return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults); + }, + awaitTransactionSuccessAsync( + txData?: Partial, + opts: AwaitTransactionSuccessOpts = { shouldValidate: true }, + ): PromiseWithTransactionHash { + return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts); + }, + async estimateGasAsync(txData?: Partial | undefined): Promise { + const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({ + ...txData, + data: this.getABIEncodedTransactionData(), + }); + return self._web3Wrapper.estimateGasAsync(txDataWithDefaults); + }, + async callAsync(callData: Partial = {}, defaultBlock?: BlockParam): Promise { + BaseContract._assertCallParams(callData, defaultBlock); + const rawCallResult = await self._performCallAsync( + { ...callData, data: this.getABIEncodedTransactionData() }, + defaultBlock, + ); + const abiEncoder = self._lookupAbiEncoder(functionSignature); + BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder); + return abiEncoder.strictDecodeReturnValue(rawCallResult); + }, + getABIEncodedTransactionData(): string { + return self._strictEncodeArguments(functionSignature, []); + }, + }; + } + /** + * Return the allowed deployer for transformers. + */ + public getTransformerDeployer(): ContractTxFunctionObj { + const self = (this as any) as ITransformERC20Contract; + const functionSignature = 'getTransformerDeployer()'; + + return { + async sendTransactionAsync( + txData?: Partial | undefined, + opts: SendTransactionOpts = { shouldValidate: true }, + ): Promise { + const txDataWithDefaults = await self._applyDefaultsToTxDataAsync( + { ...txData, data: this.getABIEncodedTransactionData() }, + this.estimateGasAsync.bind(this), + ); + if (opts.shouldValidate !== false) { + await this.callAsync(txDataWithDefaults); + } + return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults); + }, + awaitTransactionSuccessAsync( + txData?: Partial, + opts: AwaitTransactionSuccessOpts = { shouldValidate: true }, + ): PromiseWithTransactionHash { + return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts); + }, + async estimateGasAsync(txData?: Partial | undefined): Promise { + const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({ + ...txData, + data: this.getABIEncodedTransactionData(), + }); + return self._web3Wrapper.estimateGasAsync(txDataWithDefaults); + }, + async callAsync(callData: Partial = {}, defaultBlock?: BlockParam): Promise { + BaseContract._assertCallParams(callData, defaultBlock); + const rawCallResult = await self._performCallAsync( + { ...callData, data: this.getABIEncodedTransactionData() }, + defaultBlock, + ); + const abiEncoder = self._lookupAbiEncoder(functionSignature); + BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder); + return abiEncoder.strictDecodeReturnValue(rawCallResult); + }, + getABIEncodedTransactionData(): string { + return self._strictEncodeArguments(functionSignature, []); + }, + }; + } + /** + * Executes a series of transformations to convert an ERC20 `inputToken` + * to an ERC20 `outputToken`. + * @param inputToken The token being provided by the sender. If + * `0xeee...`, ETH is implied and should be provided with the call.` + * @param outputToken The token to be acquired by the sender. `0xeee...` + * implies ETH. + * @param inputTokenAmount The amount of `inputToken` to take from the sender. + * @param minOutputTokenAmount The minimum amount of `outputToken` the sender + * must receive for the entire transformation to succeed. + * @param transformations The transformations to execute on the token + * balance(s) in sequence. + */ + public transformERC20( + inputToken: string, + outputToken: string, + inputTokenAmount: BigNumber, + minOutputTokenAmount: BigNumber, + transformations: Array<{ transformer: string; data: string }>, + ): ContractTxFunctionObj { + const self = (this as any) as ITransformERC20Contract; + assert.isString('inputToken', inputToken); + assert.isString('outputToken', outputToken); + assert.isBigNumber('inputTokenAmount', inputTokenAmount); + assert.isBigNumber('minOutputTokenAmount', minOutputTokenAmount); + assert.isArray('transformations', transformations); + const functionSignature = 'transformERC20(address,address,uint256,uint256,(address,bytes)[])'; + + return { + async sendTransactionAsync( + txData?: Partial | undefined, + opts: SendTransactionOpts = { shouldValidate: true }, + ): Promise { + const txDataWithDefaults = await self._applyDefaultsToTxDataAsync( + { ...txData, data: this.getABIEncodedTransactionData() }, + this.estimateGasAsync.bind(this), + ); + if (opts.shouldValidate !== false) { + await this.callAsync(txDataWithDefaults); + } + return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults); + }, + awaitTransactionSuccessAsync( + txData?: Partial, + opts: AwaitTransactionSuccessOpts = { shouldValidate: true }, + ): PromiseWithTransactionHash { + return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts); + }, + async estimateGasAsync(txData?: Partial | undefined): Promise { + const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({ + ...txData, + data: this.getABIEncodedTransactionData(), + }); + return self._web3Wrapper.estimateGasAsync(txDataWithDefaults); + }, + async callAsync(callData: Partial = {}, defaultBlock?: BlockParam): Promise { + BaseContract._assertCallParams(callData, defaultBlock); + const rawCallResult = await self._performCallAsync( + { ...callData, data: this.getABIEncodedTransactionData() }, + defaultBlock, + ); + const abiEncoder = self._lookupAbiEncoder(functionSignature); + BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder); + return abiEncoder.strictDecodeReturnValue(rawCallResult); + }, + getABIEncodedTransactionData(): string { + return self._strictEncodeArguments(functionSignature, [ + inputToken.toLowerCase(), + outputToken.toLowerCase(), + inputTokenAmount, + minOutputTokenAmount, + transformations, + ]); + }, + }; + } + + /** + * Subscribe to an event type emitted by the ITransformERC20 contract. + * @param eventName The ITransformERC20 contract event you would like to subscribe to. + * @param indexFilterValues An object where the keys are indexed args returned by the event and + * the value is the value you are interested in. E.g `{maker: aUserAddressHex}` + * @param callback Callback that gets called when a log is added/removed + * @param isVerbose Enable verbose subscription warnings (e.g recoverable network issues encountered) + * @return Subscription token used later to unsubscribe + */ + public subscribe( + eventName: ITransformERC20Events, + indexFilterValues: IndexedFilterValues, + callback: EventCallback, + isVerbose: boolean = false, + blockPollingIntervalMs?: number, + ): string { + assert.doesBelongToStringEnum('eventName', eventName, ITransformERC20Events); + assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); + assert.isFunction('callback', callback); + const subscriptionToken = this._subscriptionManager.subscribe( + this.address, + eventName, + indexFilterValues, + ITransformERC20Contract.ABI(), + callback, + isVerbose, + blockPollingIntervalMs, + ); + return subscriptionToken; + } + + /** + * Cancel a subscription + * @param subscriptionToken Subscription token returned by `subscribe()` + */ + public unsubscribe(subscriptionToken: string): void { + this._subscriptionManager.unsubscribe(subscriptionToken); + } + + /** + * Cancels all existing subscriptions + */ + public unsubscribeAll(): void { + this._subscriptionManager.unsubscribeAll(); + } + + /** + * Gets historical logs without creating a subscription + * @param eventName The ITransformERC20 contract event you would like to subscribe to. + * @param blockRange Block range to get logs from. + * @param indexFilterValues An object where the keys are indexed args returned by the event and + * the value is the value you are interested in. E.g `{_from: aUserAddressHex}` + * @return Array of logs that match the parameters + */ + public async getLogsAsync( + eventName: ITransformERC20Events, + blockRange: BlockRange, + indexFilterValues: IndexedFilterValues, + ): Promise>> { + assert.doesBelongToStringEnum('eventName', eventName, ITransformERC20Events); + assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema); + assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); + const logs = await this._subscriptionManager.getLogsAsync( + this.address, + eventName, + blockRange, + indexFilterValues, + ITransformERC20Contract.ABI(), + ); + return logs; + } + + constructor( + address: string, + supportedProvider: SupportedProvider, + txDefaults?: Partial, + logDecodeDependencies?: { [contractName: string]: ContractAbi }, + deployedBytecode: string | undefined = ITransformERC20Contract.deployedBytecode, + ) { + super( + 'ITransformERC20', + ITransformERC20Contract.ABI(), + address, + supportedProvider, + txDefaults, + logDecodeDependencies, + deployedBytecode, + ); + classUtils.bindAll(this, ['_abiEncoderByFunctionSignature', 'address', '_web3Wrapper']); + this._subscriptionManager = new SubscriptionManager( + ITransformERC20Contract.ABI(), + this._web3Wrapper, + ); + ITransformERC20Contract.ABI().forEach((item, index) => { + if (item.type === 'function') { + const methodAbi = item as MethodAbi; + this._methodABIIndex[methodAbi.name] = index; + } + }); + } +} + +// tslint:disable:max-file-line-count +// tslint:enable:no-unbound-method no-parameter-reassignment no-consecutive-blank-lines ordered-imports align +// tslint:enable:trailing-comma whitespace no-trailing-whitespace diff --git a/packages/contract-wrappers/src/index.ts b/packages/contract-wrappers/src/index.ts index 091bfb06b5..c92d349f3e 100644 --- a/packages/contract-wrappers/src/index.ts +++ b/packages/contract-wrappers/src/index.ts @@ -83,6 +83,12 @@ export { StakingProxyStakingContractAttachedToProxyEventArgs, StakingProxyStakingContractDetachedFromProxyEventArgs, } from './generated-wrappers/staking_proxy'; +export { + ITransformERC20Contract, + ITransformERC20EventArgs, + ITransformERC20Events, + ITransformERC20TransformedERC20EventArgs, +} from './generated-wrappers/i_transform_erc20'; export { BlockRange, SupportedProvider,