|
| 1 | +/* |
| 2 | +This file is part of web3.js. |
| 3 | +
|
| 4 | +web3.js is free software: you can redistribute it and/or modify |
| 5 | +it under the terms of the GNU Lesser General Public License as published by |
| 6 | +the Free Software Foundation, either version 3 of the License, or |
| 7 | +(at your option) any later version. |
| 8 | +
|
| 9 | +web3.js is distributed in the hope that it will be useful, |
| 10 | +but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 | +GNU Lesser General Public License for more details. |
| 13 | +
|
| 14 | +You should have received a copy of the GNU Lesser General Public License |
| 15 | +along with web3.js. If not, see <http://www.gnu.org/licenses/>. |
| 16 | +*/ |
| 17 | + |
| 18 | +import { |
| 19 | + Web3ContractError, |
| 20 | +} from 'web3-errors'; |
| 21 | +import { |
| 22 | + sendTransaction, |
| 23 | + SendTransactionEvents, |
| 24 | + SendTransactionOptions, |
| 25 | +} from 'web3-eth'; |
| 26 | +import { |
| 27 | + AbiConstructorFragment, |
| 28 | + AbiFunctionFragment, |
| 29 | + ContractAbi, |
| 30 | + ContractConstructorArgs, |
| 31 | + Bytes, |
| 32 | + HexString, |
| 33 | + PayableCallOptions, |
| 34 | + DataFormat, |
| 35 | + DEFAULT_RETURN_FORMAT, |
| 36 | + ContractOptions, |
| 37 | + TransactionReceipt, |
| 38 | + TransactionCall, |
| 39 | +} from 'web3-types'; |
| 40 | +import { |
| 41 | + format, |
| 42 | +} from 'web3-utils'; |
| 43 | +import { |
| 44 | + isNullish, |
| 45 | +} from 'web3-validator'; |
| 46 | +import { Web3PromiEvent } from 'web3-core'; |
| 47 | +import { |
| 48 | + decodeMethodParams, |
| 49 | + encodeMethodABI, |
| 50 | +} from './encoding.js'; |
| 51 | +import { |
| 52 | + NonPayableTxOptions, |
| 53 | + PayableTxOptions, |
| 54 | +} from './types.js'; |
| 55 | +import { |
| 56 | + getSendTxParams, |
| 57 | +} from './utils.js'; |
| 58 | +// eslint-disable-next-line import/no-cycle |
| 59 | +import { Contract } from './contract.js'; |
| 60 | + |
| 61 | +export type ContractDeploySend<Abi extends ContractAbi> = Web3PromiEvent< |
| 62 | + // eslint-disable-next-line no-use-before-define |
| 63 | + Contract<Abi>, |
| 64 | + SendTransactionEvents<DataFormat> |
| 65 | +>; |
| 66 | + |
| 67 | +/* |
| 68 | + * This class is only supposed to be used for the return of `new Contract(...).deploy(...)` method. |
| 69 | + */ |
| 70 | +export class DeployerMethodClass<FullContractAbi extends ContractAbi> { |
| 71 | + |
| 72 | + protected readonly args: never[] | ContractConstructorArgs<FullContractAbi>; |
| 73 | + protected readonly constructorAbi: AbiConstructorFragment; |
| 74 | + protected readonly contractOptions: ContractOptions; |
| 75 | + protected readonly deployData?: string; |
| 76 | + |
| 77 | + protected _contractMethodDeploySend( |
| 78 | + tx: TransactionCall, |
| 79 | + ) { |
| 80 | + // eslint-disable-next-line no-use-before-define |
| 81 | + const returnTxOptions: SendTransactionOptions<Contract<FullContractAbi>> = { |
| 82 | + transactionResolver: (receipt: TransactionReceipt) => { |
| 83 | + if (receipt.status === BigInt(0)) { |
| 84 | + throw new Web3ContractError("code couldn't be stored", receipt); |
| 85 | + } |
| 86 | + |
| 87 | + const newContract = this.parent.clone(); |
| 88 | + |
| 89 | + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access |
| 90 | + newContract.options.address = receipt.contractAddress; |
| 91 | + return newContract; |
| 92 | + }, |
| 93 | + |
| 94 | + contractAbi: this.parent.options.jsonInterface, |
| 95 | + // TODO Should make this configurable by the user |
| 96 | + checkRevertBeforeSending: false, |
| 97 | + }; |
| 98 | + |
| 99 | + return isNullish(this.parent.getTransactionMiddleware()) |
| 100 | + ? sendTransaction(this.parent, tx, this.parent.defaultReturnFormat, returnTxOptions) // not calling this with undefined Middleware because it will not break if Eth package is not updated |
| 101 | + : sendTransaction( |
| 102 | + this.parent, |
| 103 | + tx, |
| 104 | + this.parent.defaultReturnFormat, |
| 105 | + returnTxOptions, |
| 106 | + this.parent.getTransactionMiddleware(), |
| 107 | + ); |
| 108 | + } |
| 109 | + |
| 110 | + public constructor( |
| 111 | + // eslint-disable-next-line no-use-before-define |
| 112 | + public parent: Contract<FullContractAbi>, |
| 113 | + public deployOptions: |
| 114 | + | { |
| 115 | + /** |
| 116 | + * The byte code of the contract. |
| 117 | + */ |
| 118 | + data?: HexString; |
| 119 | + input?: HexString; |
| 120 | + /** |
| 121 | + * The arguments which get passed to the constructor on deployment. |
| 122 | + */ |
| 123 | + arguments?: ContractConstructorArgs<FullContractAbi>; |
| 124 | + } |
| 125 | + | undefined, |
| 126 | + ) { |
| 127 | + |
| 128 | + const { args, abi, contractOptions, deployData} = this.calculateDeployParams(); |
| 129 | + |
| 130 | + this.args = args; |
| 131 | + this.constructorAbi = abi; |
| 132 | + this.contractOptions = contractOptions; |
| 133 | + this.deployData = deployData; |
| 134 | + } |
| 135 | + |
| 136 | + public send(options?: PayableTxOptions): ContractDeploySend<FullContractAbi> { |
| 137 | + const modifiedOptions = { ...options }; |
| 138 | + |
| 139 | + const tx = this.populateTransaction(modifiedOptions); |
| 140 | + |
| 141 | + return this._contractMethodDeploySend(tx); |
| 142 | + } |
| 143 | + |
| 144 | + public populateTransaction( |
| 145 | + txOptions?: PayableTxOptions | NonPayableTxOptions, |
| 146 | + ) { |
| 147 | + const modifiedContractOptions = { |
| 148 | + ...this.contractOptions, |
| 149 | + from: this.contractOptions.from ?? this.parent.defaultAccount ?? undefined, |
| 150 | + }; |
| 151 | + |
| 152 | + // args, abi, contractOptions, deployData |
| 153 | + |
| 154 | + const tx = getSendTxParams({ |
| 155 | + abi: this.constructorAbi, |
| 156 | + params: this.args as unknown[], |
| 157 | + options: { ...txOptions, dataInputFill: this.parent.contractDataInputFill }, |
| 158 | + contractOptions: modifiedContractOptions, |
| 159 | + }); |
| 160 | + |
| 161 | + // @ts-expect-error remove unnecessary field |
| 162 | + if (tx.dataInputFill) { |
| 163 | + // @ts-expect-error remove unnecessary field |
| 164 | + delete tx.dataInputFill; |
| 165 | + } |
| 166 | + return tx; |
| 167 | + } |
| 168 | + |
| 169 | + protected calculateDeployParams() { |
| 170 | + let abi = this.parent.options.jsonInterface.find( |
| 171 | + j => j.type === 'constructor', |
| 172 | + ) as AbiConstructorFragment; |
| 173 | + if (!abi) { |
| 174 | + abi = { |
| 175 | + type: 'constructor', |
| 176 | + stateMutability: '', |
| 177 | + } as AbiConstructorFragment; |
| 178 | + } |
| 179 | + |
| 180 | + const _input = format( |
| 181 | + { format: 'bytes' }, |
| 182 | + this.deployOptions?.input ?? this.parent.options.input, |
| 183 | + DEFAULT_RETURN_FORMAT, |
| 184 | + ); |
| 185 | + |
| 186 | + const _data = format( |
| 187 | + { format: 'bytes' }, |
| 188 | + this.deployOptions?.data ?? this.parent.options.data, |
| 189 | + DEFAULT_RETURN_FORMAT, |
| 190 | + ); |
| 191 | + |
| 192 | + if ((!_input || _input.trim() === '0x') && (!_data || _data.trim() === '0x')) { |
| 193 | + throw new Web3ContractError('contract creation without any data provided.'); |
| 194 | + } |
| 195 | + |
| 196 | + const args = this.deployOptions?.arguments ?? []; |
| 197 | + |
| 198 | + const contractOptions: ContractOptions = { |
| 199 | + ...this.parent.options, |
| 200 | + input: _input, |
| 201 | + data: _data, |
| 202 | + }; |
| 203 | + const deployData = _input ?? _data; |
| 204 | + |
| 205 | + return { args, abi, contractOptions, deployData} |
| 206 | + } |
| 207 | + |
| 208 | + public async estimateGas<ReturnFormat extends DataFormat = typeof DEFAULT_RETURN_FORMAT>( |
| 209 | + options?: PayableCallOptions, |
| 210 | + returnFormat: ReturnFormat = this.parent.defaultReturnFormat as ReturnFormat, |
| 211 | + ) { |
| 212 | + const modifiedOptions = { ...options }; |
| 213 | + return this.parent.contractMethodEstimateGas({ |
| 214 | + abi: this.constructorAbi as AbiFunctionFragment, |
| 215 | + params: this.args as unknown[], |
| 216 | + returnFormat, |
| 217 | + options: modifiedOptions, |
| 218 | + contractOptions: this.contractOptions, |
| 219 | + }); |
| 220 | + } |
| 221 | + |
| 222 | + public encodeABI() { |
| 223 | + return encodeMethodABI( |
| 224 | + this.constructorAbi, |
| 225 | + this.args as unknown[], |
| 226 | + format( |
| 227 | + { format: 'bytes' }, |
| 228 | + this.deployData as Bytes, |
| 229 | + this.parent.defaultReturnFormat as typeof DEFAULT_RETURN_FORMAT, |
| 230 | + ), |
| 231 | + ); |
| 232 | + } |
| 233 | + |
| 234 | + public decodeData(data: HexString) { |
| 235 | + return { |
| 236 | + ...decodeMethodParams(this.constructorAbi, data.replace(this.deployData as string, ''), false), |
| 237 | + __method__: this.constructorAbi.type, |
| 238 | + }; |
| 239 | + } |
| 240 | +}; |
0 commit comments