Skip to content

Commit

Permalink
Refactor some parts of contract and accounts packages (#7197)
Browse files Browse the repository at this point in the history
* add `DeployerMethodClass`

* set `extraTxTypes` using global

* update CHANGELOG.md files
  • Loading branch information
Muhammad-Altabba authored Aug 21, 2024
1 parent 4f8e8cc commit 60fc197
Show file tree
Hide file tree
Showing 7 changed files with 310 additions and 200 deletions.
3 changes: 3 additions & 0 deletions packages/web3-eth-accounts/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,6 @@ Documentation:
### Added

- Added public function `signMessageWithPrivateKey` (#7174)

### Fixed
- Fix `TransactionFactory.registerTransactionType` not working, if there is a version mistatch between `web3-eth` and `web3-eth-accounts` by saving `extraTxTypes` at `globals`. (#7197)
10 changes: 9 additions & 1 deletion packages/web3-eth-accounts/src/tx/transactionFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,15 @@ import type {
} from './types.js';
import { BaseTransaction } from './baseTransaction.js';

const extraTxTypes: Map<Numbers, typeof BaseTransaction<unknown>> = new Map();
let extraTxTypes: Map<Numbers, typeof BaseTransaction<unknown>>;
// use the global object, to work fine even if web3-eth and web3-eth-accounts was on a different versions:
const typedGlobal = global as unknown as {extraTxTypes: Map<Numbers, typeof BaseTransaction<unknown>>}
if (!typedGlobal.extraTxTypes) {
extraTxTypes = new Map();
typedGlobal.extraTxTypes = extraTxTypes;
} else {
extraTxTypes = typedGlobal.extraTxTypes;
}

// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class TransactionFactory {
Expand Down
8 changes: 8 additions & 0 deletions packages/web3-eth-contract/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,3 +395,11 @@ Documentation:

## [Unreleased]

### Added

- Added `populateTransaction` to the `contract.deploy(...)` properties. (#7197)

### Changed

- The returnred properties of `contract.deploy(...)` are structured with a newly created class named `DeployerMethodClass`. (#7197)
- Add a missed accepted type for the `abi` parameter, at `dataInputEncodeMethodHelper` and `getSendTxParams`. (#7197)
240 changes: 240 additions & 0 deletions packages/web3-eth-contract/src/contract-deployer-method-class.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
/*
This file is part of web3.js.
web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/

import {
Web3ContractError,
} from 'web3-errors';
import {
sendTransaction,
SendTransactionEvents,
SendTransactionOptions,
} from 'web3-eth';
import {
AbiConstructorFragment,
AbiFunctionFragment,
ContractAbi,
ContractConstructorArgs,
Bytes,
HexString,
PayableCallOptions,
DataFormat,
DEFAULT_RETURN_FORMAT,
ContractOptions,
TransactionReceipt,
TransactionCall,
} from 'web3-types';
import {
format,
} from 'web3-utils';
import {
isNullish,
} from 'web3-validator';
import { Web3PromiEvent } from 'web3-core';
import {
decodeMethodParams,
encodeMethodABI,
} from './encoding.js';
import {
NonPayableTxOptions,
PayableTxOptions,
} from './types.js';
import {
getSendTxParams,
} from './utils.js';
// eslint-disable-next-line import/no-cycle
import { Contract } from './contract.js';

export type ContractDeploySend<Abi extends ContractAbi> = Web3PromiEvent<
// eslint-disable-next-line no-use-before-define
Contract<Abi>,
SendTransactionEvents<DataFormat>
>;

/*
* This class is only supposed to be used for the return of `new Contract(...).deploy(...)` method.
*/
export class DeployerMethodClass<FullContractAbi extends ContractAbi> {

protected readonly args: never[] | ContractConstructorArgs<FullContractAbi>;
protected readonly constructorAbi: AbiConstructorFragment;
protected readonly contractOptions: ContractOptions;
protected readonly deployData?: string;

protected _contractMethodDeploySend(
tx: TransactionCall,
) {
// eslint-disable-next-line no-use-before-define
const returnTxOptions: SendTransactionOptions<Contract<FullContractAbi>> = {
transactionResolver: (receipt: TransactionReceipt) => {
if (receipt.status === BigInt(0)) {
throw new Web3ContractError("code couldn't be stored", receipt);
}

const newContract = this.parent.clone();

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
newContract.options.address = receipt.contractAddress;
return newContract;
},

contractAbi: this.parent.options.jsonInterface,
// TODO Should make this configurable by the user
checkRevertBeforeSending: false,
};

return isNullish(this.parent.getTransactionMiddleware())
? 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
: sendTransaction(
this.parent,
tx,
this.parent.defaultReturnFormat,
returnTxOptions,
this.parent.getTransactionMiddleware(),
);
}

public constructor(
// eslint-disable-next-line no-use-before-define
public parent: Contract<FullContractAbi>,
public deployOptions:
| {
/**
* The byte code of the contract.
*/
data?: HexString;
input?: HexString;
/**
* The arguments which get passed to the constructor on deployment.
*/
arguments?: ContractConstructorArgs<FullContractAbi>;
}
| undefined,
) {

const { args, abi, contractOptions, deployData} = this.calculateDeployParams();

this.args = args;
this.constructorAbi = abi;
this.contractOptions = contractOptions;
this.deployData = deployData;
}

public send(options?: PayableTxOptions): ContractDeploySend<FullContractAbi> {
const modifiedOptions = { ...options };

const tx = this.populateTransaction(modifiedOptions);

return this._contractMethodDeploySend(tx);
}

public populateTransaction(
txOptions?: PayableTxOptions | NonPayableTxOptions,
) {
const modifiedContractOptions = {
...this.contractOptions,
from: this.contractOptions.from ?? this.parent.defaultAccount ?? undefined,
};

// args, abi, contractOptions, deployData

const tx = getSendTxParams({
abi: this.constructorAbi,
params: this.args as unknown[],
options: { ...txOptions, dataInputFill: this.parent.contractDataInputFill },
contractOptions: modifiedContractOptions,
});

// @ts-expect-error remove unnecessary field
if (tx.dataInputFill) {
// @ts-expect-error remove unnecessary field
delete tx.dataInputFill;
}
return tx;
}

protected calculateDeployParams() {
let abi = this.parent.options.jsonInterface.find(
j => j.type === 'constructor',
) as AbiConstructorFragment;
if (!abi) {
abi = {
type: 'constructor',
stateMutability: '',
} as AbiConstructorFragment;
}

const _input = format(
{ format: 'bytes' },
this.deployOptions?.input ?? this.parent.options.input,
DEFAULT_RETURN_FORMAT,
);

const _data = format(
{ format: 'bytes' },
this.deployOptions?.data ?? this.parent.options.data,
DEFAULT_RETURN_FORMAT,
);

if ((!_input || _input.trim() === '0x') && (!_data || _data.trim() === '0x')) {
throw new Web3ContractError('contract creation without any data provided.');
}

const args = this.deployOptions?.arguments ?? [];

const contractOptions: ContractOptions = {
...this.parent.options,
input: _input,
data: _data,
};
const deployData = _input ?? _data;

return { args, abi, contractOptions, deployData}
}

public async estimateGas<ReturnFormat extends DataFormat = typeof DEFAULT_RETURN_FORMAT>(
options?: PayableCallOptions,
returnFormat: ReturnFormat = this.parent.defaultReturnFormat as ReturnFormat,
) {
const modifiedOptions = { ...options };
return this.parent.contractMethodEstimateGas({
abi: this.constructorAbi as AbiFunctionFragment,
params: this.args as unknown[],
returnFormat,
options: modifiedOptions,
contractOptions: this.contractOptions,
});
}

public encodeABI() {
return encodeMethodABI(
this.constructorAbi,
this.args as unknown[],
format(
{ format: 'bytes' },
this.deployData as Bytes,
this.parent.defaultReturnFormat as typeof DEFAULT_RETURN_FORMAT,
),
);
}

public decodeData(data: HexString) {
return {
...decodeMethodParams(this.constructorAbi, data.replace(this.deployData as string, ''), false),
__method__: this.constructorAbi.type,
};
}
};
Loading

1 comment on commit 60fc197

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Benchmark suite Current: 60fc197 Previous: 4f8e8cc Ratio
processingTx 23607 ops/sec (±6.40%) 21886 ops/sec (±6.09%) 0.93
processingContractDeploy 40397 ops/sec (±9.19%) 38750 ops/sec (±9.06%) 0.96
processingContractMethodSend 17346 ops/sec (±6.97%) 15198 ops/sec (±8.03%) 0.88
processingContractMethodCall 28617 ops/sec (±7.82%) 26542 ops/sec (±7.32%) 0.93
abiEncode 45490 ops/sec (±6.79%) 40956 ops/sec (±7.46%) 0.90
abiDecode 31146 ops/sec (±7.38%) 29461 ops/sec (±6.44%) 0.95
sign 1551 ops/sec (±0.83%) 1499 ops/sec (±3.97%) 0.97
verify 361 ops/sec (±0.56%) 355 ops/sec (±0.76%) 0.98

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.