diff --git a/.solhint.json b/.solhint.json index 03fea972..09af5b03 100644 --- a/.solhint.json +++ b/.solhint.json @@ -3,12 +3,14 @@ "plugins": ["prettier"], "rules": { "prettier/prettier": "warn", - "reentrancy": "off", - "modifier-name-mixedcase": "off", + "reentrancy": "warn", + "modifier-name-mixedcase": "warn", "no-empty-blocks": "off", - "func-visibility": "off", - "max-states-count": "off", + "func-visibility": ["warn", { "ignoreConstructors": true }], + "max-states-count": "warn", "not-rely-on-time": "off", - "compiler-version": "off" + "func-name-mixedcase": "off", + "no-inline-assembly": "off", + "compiler-version": ["off"] } } diff --git a/README.md b/README.md index af367877..812f105b 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![GitPOAP Badge](https://public-api.gitpoap.io/v1/repo/dl-solarity/solidity-lib/badge)](https://www.gitpoap.io/gh/dl-solarity/solidity-lib) -# Solidity Library for Savvies +# Solarity Solidity Library Solidity modules and utilities that **go far beyond mediocre solidity**. @@ -21,7 +21,7 @@ Solidity modules and utilities that **go far beyond mediocre solidity**. - Hyperoptimized **uint512** BigInt library - Utilities to ease work with memory, types, ERC20 decimals, arrays, sets, and ZK proofs -Built leveraging [OpenZeppelin Contracts](https://github.com/OpenZeppelin/openzeppelin-contracts) (4.9.6). +Built with courage and aspiration to perfection. ## Overview @@ -44,17 +44,44 @@ You will find the smart contracts in the `/contracts` directory. Feel free to pl Once the [npm package](https://www.npmjs.com/package/@solarity/solidity-lib) is installed, one can use the library just like that: ```solidity -pragma solidity ^0.8.4; - -import {OwnableContractsRegistry} from "@solarity/solidity-lib/contracts-registry/presets/OwnableContractsRegistry.sol"; - -contract ContractsRegistry is OwnableContractsRegistry { - . . . +pragma solidity ^0.8.21; + +import {AMultiOwnable} from "@solarity/solidity-lib/access/AMultiOwnable.sol"; +import {TypeCaster} from "@solarity/solidity-lib/libs/utils/TypeCaster.sol"; +import {CartesianMerkleTree} from "@solarity/solidity-lib/libs/data-structures/CartesianMerkleTree.sol"; +import {Groth16VerifierHelper} from "@solarity/solidity-lib/libs/zkp/Groth16VerifierHelper.sol"; + +contract Example is AMultiOwnable { + using CartesianMerkleTree for CartesianMerkleTree.UintCMT; + using Groth16VerifierHelper for address; + + CartesianMerkleTree.UintCMT internal _uintTreaple; + address internal _treapleVerifier; + + function __Example_init(address treapleVerifier_) initializer { + __AMultiOwnable_init(); + _uintTreaple.initialize(40); + _treapleVerifier = treapleVerifier_; + } + + function addToTree(uint256 key_) external onlyOwner { + _uintTreaple.add(key_); + } + + function getMerkleProof(uint256 key_) external view returns (CartesianMerkleTree.Proof memory) { + return _uintTreaple.getProof(key_, 0); + } + + function verifyZKProof(Groth16VerifierHelper.ProofPoints memory proof_) external view { + uint256[] memory pubSignals_ = TypeCaster.asSingletonArray(_uintTreaple.getRoot()); + + require(_treapleVerifier.verifyProof(pubSignals_, proof_), "ZKP verification failed"); + } } ``` -> [!IMPORTANT] -> It is important to use the library as it is shipped and not copy-paste the code from untrusted sources. +> [!TIP] +> The library is designed to work cohesively with [hardhat-zkit](https://github.com/dl-solarity/hardhat-zkit) and [circom-lib](https://github.com/dl-solarity/circom-lib) packages. ## Contribution diff --git a/contracts/access/MerkleWhitelisted.sol b/contracts/access/AMerkleWhitelisted.sol similarity index 88% rename from contracts/access/MerkleWhitelisted.sol rename to contracts/access/AMerkleWhitelisted.sol index c4179257..54d10e31 100644 --- a/contracts/access/MerkleWhitelisted.sol +++ b/contracts/access/AMerkleWhitelisted.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; @@ -17,21 +17,21 @@ import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProo * * Note: the branch nodes are sorted numerically. */ -abstract contract MerkleWhitelisted { +abstract contract AMerkleWhitelisted { using MerkleProof for bytes32[]; bytes32 private _merkleRoot; + error LeafNotWhitelisted(bytes data); + error UserNotWhitelisted(address user); + modifier onlyWhitelisted(bytes memory data_, bytes32[] memory merkleProof_) { - require( - _isWhitelisted(keccak256(data_), merkleProof_), - "MerkleWhitelisted: not whitelisted" - ); + if (!_isWhitelisted(keccak256(data_), merkleProof_)) revert LeafNotWhitelisted(data_); _; } modifier onlyWhitelistedUser(address user_, bytes32[] memory merkleProof_) { - require(_isWhitelistedUser(user_, merkleProof_), "MerkleWhitelisted: not whitelisted"); + if (!_isWhitelistedUser(user_, merkleProof_)) revert UserNotWhitelisted(user_); _; } diff --git a/contracts/access/MultiOwnable.sol b/contracts/access/AMultiOwnable.sol similarity index 91% rename from contracts/access/MultiOwnable.sol rename to contracts/access/AMultiOwnable.sol index 4e432807..416b8b32 100644 --- a/contracts/access/MultiOwnable.sol +++ b/contracts/access/AMultiOwnable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; @@ -21,13 +21,16 @@ import {IMultiOwnable} from "../interfaces/access/IMultiOwnable.sol"; * This module will make available the modifier `onlyOwner`, which can be applied * to your functions to restrict their use to the owners. */ -abstract contract MultiOwnable is IMultiOwnable, Initializable { +abstract contract AMultiOwnable is IMultiOwnable, Initializable { using EnumerableSet for EnumerableSet.AddressSet; using TypeCaster for address; using SetHelper for EnumerableSet.AddressSet; EnumerableSet.AddressSet private _owners; + error InvalidOwner(); + error UnauthorizedAccount(address account); + modifier onlyOwner() { _checkOwner(); _; @@ -36,7 +39,7 @@ abstract contract MultiOwnable is IMultiOwnable, Initializable { /** * @dev Initializes the contract setting the msg.sender as the initial owner. */ - function __MultiOwnable_init() internal onlyInitializing { + function __AMultiOwnable_init() internal onlyInitializing { _addOwners(msg.sender.asSingletonArray()); } @@ -91,7 +94,7 @@ abstract contract MultiOwnable is IMultiOwnable, Initializable { function _addOwners(address[] memory newOwners_) private { _owners.add(newOwners_); - require(!_owners.contains(address(0)), "MultiOwnable: zero address can not be added"); + if (_owners.contains(address(0))) revert InvalidOwner(); emit OwnersAdded(newOwners_); } @@ -115,6 +118,6 @@ abstract contract MultiOwnable is IMultiOwnable, Initializable { * @dev Throws if the sender is not the owner. */ function _checkOwner() private view { - require(isOwner(msg.sender), "MultiOwnable: caller is not the owner"); + if (!isOwner(msg.sender)) revert UnauthorizedAccount(msg.sender); } } diff --git a/contracts/access/PermanentOwnable.sol b/contracts/access/APermanentOwnable.sol similarity index 81% rename from contracts/access/PermanentOwnable.sol rename to contracts/access/APermanentOwnable.sol index e71e3284..f695b4e9 100644 --- a/contracts/access/PermanentOwnable.sol +++ b/contracts/access/APermanentOwnable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @notice The PermanentOwnable module @@ -12,9 +12,12 @@ pragma solidity ^0.8.4; * This module will make available the modifier `onlyOwner`, which can be applied * to your functions to restrict their use to the owners. */ -abstract contract PermanentOwnable { +abstract contract APermanentOwnable { address private immutable _OWNER; + error InvalidOwner(); + error UnauthorizedAccount(address account); + /** * @dev Throws if called by any account other than the owner. */ @@ -28,7 +31,7 @@ abstract contract PermanentOwnable { * @param owner_ the address of the permanent owner. */ constructor(address owner_) { - require(owner_ != address(0), "PermanentOwnable: zero address can not be the owner"); + if (owner_ == address(0)) revert InvalidOwner(); _OWNER = owner_; } @@ -42,6 +45,6 @@ abstract contract PermanentOwnable { } function _onlyOwner() internal view virtual { - require(_OWNER == msg.sender, "PermanentOwnable: caller is not the owner"); + if (_OWNER != msg.sender) revert UnauthorizedAccount(msg.sender); } } diff --git a/contracts/access/RBAC.sol b/contracts/access/ARBAC.sol similarity index 96% rename from contracts/access/RBAC.sol rename to contracts/access/ARBAC.sol index 234044f2..9dc39e1e 100644 --- a/contracts/access/RBAC.sol +++ b/contracts/access/ARBAC.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; @@ -30,7 +30,7 @@ import {DynamicSet} from "../libs/data-structures/DynamicSet.sol"; * * Where ROLE is assignable to users */ -abstract contract RBAC is IRBAC, Initializable { +abstract contract ARBAC is IRBAC, Initializable { using DynamicSet for DynamicSet.StringSet; using SetHelper for DynamicSet.StringSet; using TypeCaster for string; @@ -53,20 +53,19 @@ abstract contract RBAC is IRBAC, Initializable { mapping(address => DynamicSet.StringSet) private _userRoles; + error EmptyRoles(); + error NoPermissionForResource(address account, string permission, string resource); + modifier onlyPermission(string memory resource_, string memory permission_) { - require( - hasPermission(msg.sender, resource_, permission_), - string( - abi.encodePacked("RBAC: no ", permission_, " permission for resource ", resource_) - ) - ); + if (!hasPermission(msg.sender, resource_, permission_)) + revert NoPermissionForResource(msg.sender, permission_, resource_); _; } /** * @notice The initialization function */ - function __RBAC_init() internal onlyInitializing { + function __ARBAC_init() internal onlyInitializing { _addPermissionsToRole(MASTER_ROLE, ALL_RESOURCE, ALL_PERMISSION.asSingletonArray(), true); } @@ -79,7 +78,7 @@ abstract contract RBAC is IRBAC, Initializable { address to_, string[] memory rolesToGrant_ ) public virtual override onlyPermission(RBAC_RESOURCE, CREATE_PERMISSION) { - require(rolesToGrant_.length > 0, "RBAC: empty roles"); + if (rolesToGrant_.length == 0) revert EmptyRoles(); _grantRoles(to_, rolesToGrant_); } @@ -93,7 +92,7 @@ abstract contract RBAC is IRBAC, Initializable { address from_, string[] memory rolesToRevoke_ ) public virtual override onlyPermission(RBAC_RESOURCE, DELETE_PERMISSION) { - require(rolesToRevoke_.length > 0, "RBAC: empty roles"); + if (rolesToRevoke_.length == 0) revert EmptyRoles(); _revokeRoles(from_, rolesToRevoke_); } diff --git a/contracts/access/extensions/RBACGroupable.sol b/contracts/access/extensions/ARBACGroupable.sol similarity index 93% rename from contracts/access/extensions/RBACGroupable.sol rename to contracts/access/extensions/ARBACGroupable.sol index 4bb9a44f..0c1e2a73 100644 --- a/contracts/access/extensions/RBACGroupable.sol +++ b/contracts/access/extensions/ARBACGroupable.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {IRBACGroupable} from "../../interfaces/access/extensions/IRBACGroupable.sol"; import {DynamicSet} from "../../libs/data-structures/DynamicSet.sol"; import {SetHelper} from "../../libs/arrays/SetHelper.sol"; -import {RBAC} from "../RBAC.sol"; +import {ARBAC} from "../ARBAC.sol"; /** * @notice The Role Based Access Control (RBAC) module @@ -22,7 +22,7 @@ import {RBAC} from "../RBAC.sol"; * * Where ROLE and GROUP are assignable to users */ -abstract contract RBACGroupable is IRBACGroupable, RBAC { +abstract contract ARBACGroupable is IRBACGroupable, ARBAC { using DynamicSet for DynamicSet.StringSet; using SetHelper for DynamicSet.StringSet; @@ -31,11 +31,13 @@ abstract contract RBACGroupable is IRBACGroupable, RBAC { mapping(address => DynamicSet.StringSet) private _userGroups; mapping(string => DynamicSet.StringSet) private _groupRoles; + error EmptyGroups(); + /** * @notice The initialization function */ - function __RBACGroupable_init() internal onlyInitializing { - __RBAC_init(); + function __ARBACGroupable_init() internal onlyInitializing { + __ARBAC_init(); } /** @@ -47,7 +49,7 @@ abstract contract RBACGroupable is IRBACGroupable, RBAC { address who_, string[] memory groupsToAddTo_ ) public virtual override onlyPermission(RBAC_RESOURCE, CREATE_PERMISSION) { - require(groupsToAddTo_.length > 0, "RBACGroupable: empty groups"); + if (groupsToAddTo_.length == 0) revert EmptyGroups(); _addUserToGroups(who_, groupsToAddTo_); } @@ -61,7 +63,7 @@ abstract contract RBACGroupable is IRBACGroupable, RBAC { address who_, string[] memory groupsToRemoveFrom_ ) public virtual override onlyPermission(RBAC_RESOURCE, DELETE_PERMISSION) { - require(groupsToRemoveFrom_.length > 0, "RBACGroupable: empty groups"); + if (groupsToRemoveFrom_.length == 0) revert EmptyGroups(); _removeUserFromGroups(who_, groupsToRemoveFrom_); } @@ -75,7 +77,7 @@ abstract contract RBACGroupable is IRBACGroupable, RBAC { string memory groupTo_, string[] memory rolesToGrant_ ) public virtual override onlyPermission(RBAC_RESOURCE, CREATE_PERMISSION) { - require(rolesToGrant_.length > 0, "RBACGroupable: empty roles"); + if (rolesToGrant_.length == 0) revert EmptyRoles(); _grantGroupRoles(groupTo_, rolesToGrant_); } @@ -89,7 +91,7 @@ abstract contract RBACGroupable is IRBACGroupable, RBAC { string memory groupFrom_, string[] memory rolesToRevoke_ ) public virtual override onlyPermission(RBAC_RESOURCE, DELETE_PERMISSION) { - require(rolesToRevoke_.length > 0, "RBACGroupable: empty roles"); + if (rolesToRevoke_.length == 0) revert EmptyRoles(); _revokeGroupRoles(groupFrom_, rolesToRevoke_); } diff --git a/contracts/contracts-registry/AbstractContractsRegistry.sol b/contracts/contracts-registry/AContractsRegistry.sol similarity index 82% rename from contracts/contracts-registry/AbstractContractsRegistry.sol rename to contracts/contracts-registry/AContractsRegistry.sol index 659e30d1..bca3d214 100644 --- a/contracts/contracts-registry/AbstractContractsRegistry.sol +++ b/contracts/contracts-registry/AContractsRegistry.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.22; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import {TransparentProxyUpgrader} from "../proxy/transparent/TransparentProxyUpgrader.sol"; -import {AbstractDependant} from "./AbstractDependant.sol"; +import {AdminableProxyUpgrader} from "../proxy/adminable/AdminableProxyUpgrader.sol"; +import {AdminableProxy} from "../proxy/adminable/AdminableProxy.sol"; +import {ADependant} from "./ADependant.sol"; /** * @notice The ContractsRegistry module @@ -17,7 +17,7 @@ import {AbstractDependant} from "./AbstractDependant.sol"; * * The ContractsRegistry should be used as the highest level smart contract that is aware of any other * contract present in the system. The contracts that demand other system's contracts would then inherit - * special `AbstractDependant` contract and override `setDependencies()` function to enable ContractsRegistry + * special `ADependant` contract and override `setDependencies()` function to enable ContractsRegistry * to inject dependencies into them. * * The ContractsRegistry will help with the following use cases: @@ -26,7 +26,7 @@ import {AbstractDependant} from "./AbstractDependant.sol"; * 2) Making the system contracts-interchangeable * 3) Simplifying the contracts management and deployment * - * The ContractsRegistry acts as a TransparentProxy deployer. One can add proxy-compatible implementations to the registry + * The ContractsRegistry acts as a AdminableProxy deployer. One can add proxy-compatible implementations to the registry * and deploy proxies to them. Then these proxies can be upgraded easily using the provided interface. * The ContractsRegistry itself can be deployed behind a proxy as well. * @@ -37,8 +37,8 @@ import {AbstractDependant} from "./AbstractDependant.sol"; * * Users may also fetch all the contracts present in the system as they are now located in a single place. */ -abstract contract AbstractContractsRegistry is Initializable { - TransparentProxyUpgrader private _proxyUpgrader; +abstract contract AContractsRegistry is Initializable { + AdminableProxyUpgrader private _proxyUpgrader; mapping(string => address) private _contracts; mapping(address => bool) private _isProxy; @@ -48,11 +48,15 @@ abstract contract AbstractContractsRegistry is Initializable { event ProxyContractUpgraded(string name, address newImplementation); event ContractRemoved(string name); + error NoMappingExists(string contractName); + error NotAProxy(string contractName, address contractProxy); + error ZeroAddressProvided(string contractName); + /** * @notice The initialization function */ - function __ContractsRegistry_init() internal onlyInitializing { - _proxyUpgrader = new TransparentProxyUpgrader(); + function __AContractsRegistry_init() internal onlyInitializing { + _proxyUpgrader = new AdminableProxyUpgrader(); } /** @@ -63,7 +67,7 @@ abstract contract AbstractContractsRegistry is Initializable { function getContract(string memory name_) public view returns (address) { address contractAddress_ = _contracts[name_]; - require(contractAddress_ != address(0), "ContractsRegistry: this mapping doesn't exist"); + _checkIfMappingExist(contractAddress_, name_); return contractAddress_; } @@ -93,8 +97,8 @@ abstract contract AbstractContractsRegistry is Initializable { function getImplementation(string memory name_) public view returns (address) { address contractProxy_ = _contracts[name_]; - require(contractProxy_ != address(0), "ContractsRegistry: this mapping doesn't exist"); - require(_isProxy[contractProxy_], "ContractsRegistry: not a proxy contract"); + if (contractProxy_ == address(0)) revert NoMappingExists(name_); + if (!_isProxy[contractProxy_]) revert NotAProxy(name_, contractProxy_); return _proxyUpgrader.getImplementation(contractProxy_); } @@ -118,9 +122,9 @@ abstract contract AbstractContractsRegistry is Initializable { ) internal virtual { address contractAddress_ = _contracts[name_]; - require(contractAddress_ != address(0), "ContractsRegistry: this mapping doesn't exist"); + _checkIfMappingExist(contractAddress_, name_); - AbstractDependant dependant_ = AbstractDependant(contractAddress_); + ADependant dependant_ = ADependant(contractAddress_); dependant_.setDependencies(address(this), data_); } @@ -150,8 +154,8 @@ abstract contract AbstractContractsRegistry is Initializable { ) internal virtual { address contractToUpgrade_ = _contracts[name_]; - require(contractToUpgrade_ != address(0), "ContractsRegistry: this mapping doesn't exist"); - require(_isProxy[contractToUpgrade_], "ContractsRegistry: not a proxy contract"); + if (contractToUpgrade_ == address(0)) revert NoMappingExists(name_); + if (!_isProxy[contractToUpgrade_]) revert NotAProxy(name_, contractToUpgrade_); _proxyUpgrader.upgrade(contractToUpgrade_, newImplementation_, data_); @@ -165,7 +169,7 @@ abstract contract AbstractContractsRegistry is Initializable { * @param contractAddress_ the address of the contract */ function _addContract(string memory name_, address contractAddress_) internal virtual { - require(contractAddress_ != address(0), "ContractsRegistry: zero address is forbidden"); + if (contractAddress_ == address(0)) revert ZeroAddressProvided(name_); _contracts[name_] = contractAddress_; @@ -194,7 +198,7 @@ abstract contract AbstractContractsRegistry is Initializable { address contractAddress_, bytes memory data_ ) internal virtual { - require(contractAddress_ != address(0), "ContractsRegistry: zero address is forbidden"); + if (contractAddress_ == address(0)) revert ZeroAddressProvided(name_); address proxyAddr_ = _deployProxy(contractAddress_, address(_proxyUpgrader), data_); @@ -215,7 +219,7 @@ abstract contract AbstractContractsRegistry is Initializable { string memory name_, address contractAddress_ ) internal virtual { - require(contractAddress_ != address(0), "ContractsRegistry: zero address is forbidden"); + if (contractAddress_ == address(0)) revert ZeroAddressProvided(name_); _contracts[name_] = contractAddress_; _isProxy[contractAddress_] = true; @@ -234,7 +238,7 @@ abstract contract AbstractContractsRegistry is Initializable { function _removeContract(string memory name_) internal virtual { address contractAddress_ = _contracts[name_]; - require(contractAddress_ != address(0), "ContractsRegistry: this mapping doesn't exist"); + _checkIfMappingExist(contractAddress_, name_); delete _isProxy[contractAddress_]; delete _contracts[name_]; @@ -254,6 +258,10 @@ abstract contract AbstractContractsRegistry is Initializable { address admin_, bytes memory data_ ) internal virtual returns (address) { - return address(new TransparentUpgradeableProxy(contractAddress_, admin_, data_)); + return address(new AdminableProxy(contractAddress_, admin_, data_)); + } + + function _checkIfMappingExist(address contractAddress_, string memory name_) internal pure { + if (contractAddress_ == address(0)) revert NoMappingExists(name_); } } diff --git a/contracts/contracts-registry/AbstractDependant.sol b/contracts/contracts-registry/ADependant.sol similarity index 88% rename from contracts/contracts-registry/AbstractDependant.sol rename to contracts/contracts-registry/ADependant.sol index 451478a0..79b1f151 100644 --- a/contracts/contracts-registry/AbstractDependant.sol +++ b/contracts/contracts-registry/ADependant.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @notice The ContractsRegistry module @@ -8,9 +8,9 @@ pragma solidity ^0.8.4; * Upon the injection, the Injector (ContractsRegistry most of the time) will call the `setDependencies()` function. * The dependant contract will have to pull the required addresses from the supplied ContractsRegistry as a parameter. * - * The AbstractDependant is fully compatible with proxies courtesy of custom storage slot. + * The ADependant is fully compatible with proxies courtesy of custom storage slot. */ -abstract contract AbstractDependant { +abstract contract ADependant { /** * @notice The slot where the dependency injector is located. * @@ -22,6 +22,8 @@ abstract contract AbstractDependant { bytes32 private constant _INJECTOR_SLOT = 0x3d1f25f1ac447e55e7fec744471c4dab1c6a2b6ffb897825f9ea3d2e8c9be583; + error NotAnInjector(address injector, address caller); + modifier dependant() { _checkInjector(); _; @@ -76,6 +78,7 @@ abstract contract AbstractDependant { function _checkInjector() internal view { address injector_ = getInjector(); - require(injector_ == address(0) || injector_ == msg.sender, "Dependant: not an injector"); + if (injector_ != address(0) && injector_ != msg.sender) + revert NotAnInjector(injector_, msg.sender); } } diff --git a/contracts/contracts-registry/pools/AbstractPoolContractsRegistry.sol b/contracts/contracts-registry/pools/APoolContractsRegistry.sol similarity index 90% rename from contracts/contracts-registry/pools/AbstractPoolContractsRegistry.sol rename to contracts/contracts-registry/pools/APoolContractsRegistry.sol index 0ff1e192..47604424 100644 --- a/contracts/contracts-registry/pools/AbstractPoolContractsRegistry.sol +++ b/contracts/contracts-registry/pools/APoolContractsRegistry.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; @@ -7,7 +7,7 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {Paginator} from "../../libs/arrays/Paginator.sol"; -import {AbstractDependant} from "../../contracts-registry/AbstractDependant.sol"; +import {ADependant} from "../../contracts-registry/ADependant.sol"; import {ProxyBeacon} from "../../proxy/beacon/ProxyBeacon.sol"; @@ -24,7 +24,7 @@ import {ProxyBeacon} from "../../proxy/beacon/ProxyBeacon.sol"; * The factory contract would deploy BeaconProxies that point to these ProxyBeacons, allowing simple and cheap * upgradeability mechanics. */ -abstract contract AbstractPoolContractsRegistry is Initializable, AbstractDependant { +abstract contract APoolContractsRegistry is Initializable, ADependant { using EnumerableSet for EnumerableSet.AddressSet; using Paginator for EnumerableSet.AddressSet; using Math for uint256; @@ -34,10 +34,14 @@ abstract contract AbstractPoolContractsRegistry is Initializable, AbstractDepend mapping(string => ProxyBeacon) private _beacons; mapping(string => EnumerableSet.AddressSet) private _pools; // name => pool + error NoMappingExists(string poolName); + error NoPoolsToInject(string poolName); + error ProxyDoesNotExist(string poolName); + /** * @notice The proxy initializer function */ - function __PoolContractsRegistry_init() internal onlyInitializing {} + function __APoolContractsRegistry_init() internal onlyInitializing {} /** * @notice The function that accepts dependencies from the ContractsRegistry, can be overridden @@ -51,7 +55,7 @@ abstract contract AbstractPoolContractsRegistry is Initializable, AbstractDepend } /** - * @notice The function to add new pools into the registry. Gets called from PoolFactory + * @notice The function to add new pools into the registry. Gets called from APoolFactory * * Proper only factory access control must be added in descending contracts + `_addProxyPool()` should be called inside. * @@ -66,10 +70,7 @@ abstract contract AbstractPoolContractsRegistry is Initializable, AbstractDepend * @return address_ the implementation these pools point to */ function getImplementation(string memory name_) public view returns (address) { - require( - address(_beacons[name_]) != address(0), - "PoolContractsRegistry: this mapping doesn't exist" - ); + if (address(_beacons[name_]) == address(0)) revert NoMappingExists(name_); return _beacons[name_].implementation(); } @@ -82,7 +83,7 @@ abstract contract AbstractPoolContractsRegistry is Initializable, AbstractDepend function getProxyBeacon(string memory name_) public view returns (address) { address beacon_ = address(_beacons[name_]); - require(beacon_ != address(0), "PoolContractsRegistry: bad ProxyBeacon"); + if (beacon_ == address(0)) revert ProxyDoesNotExist(name_); return beacon_; } @@ -173,12 +174,12 @@ abstract contract AbstractPoolContractsRegistry is Initializable, AbstractDepend uint256 to_ = (offset_ + limit_).min(_namedPools.length()).max(offset_); - require(to_ != offset_, "PoolContractsRegistry: no pools to inject"); + if (to_ == offset_) revert NoPoolsToInject(name_); address contractsRegistry_ = _contractsRegistry; for (uint256 i = offset_; i < to_; i++) { - AbstractDependant(_namedPools.at(i)).setDependencies(contractsRegistry_, data_); + ADependant(_namedPools.at(i)).setDependencies(contractsRegistry_, data_); } } diff --git a/contracts/contracts-registry/pools/pool-factory/AbstractPoolFactory.sol b/contracts/contracts-registry/pools/pool-factory/APoolFactory.sol similarity index 80% rename from contracts/contracts-registry/pools/pool-factory/AbstractPoolFactory.sol rename to contracts/contracts-registry/pools/pool-factory/APoolFactory.sol index eb30fe52..384c06c6 100644 --- a/contracts/contracts-registry/pools/pool-factory/AbstractPoolFactory.sol +++ b/contracts/contracts-registry/pools/pool-factory/APoolFactory.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.22; import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; -import {AbstractDependant} from "../../../contracts-registry/AbstractDependant.sol"; -import {AbstractPoolContractsRegistry} from "../AbstractPoolContractsRegistry.sol"; +import {ADependant} from "../../../contracts-registry/ADependant.sol"; +import {APoolContractsRegistry} from "../APoolContractsRegistry.sol"; import {PublicBeaconProxy} from "../../../proxy/beacon/PublicBeaconProxy.sol"; @@ -19,7 +19,7 @@ import {PublicBeaconProxy} from "../../../proxy/beacon/PublicBeaconProxy.sol"; * * Both "create1" and "create2" deployment modes are supported. */ -abstract contract AbstractPoolFactory is AbstractDependant { +abstract contract APoolFactory is ADependant { address internal _contractsRegistry; /** @@ -44,7 +44,7 @@ abstract contract AbstractPoolFactory is AbstractDependant { return address( new PublicBeaconProxy( - AbstractPoolContractsRegistry(poolRegistry_).getProxyBeacon(poolType_), + APoolContractsRegistry(poolRegistry_).getProxyBeacon(poolType_), bytes("") ) ); @@ -62,7 +62,7 @@ abstract contract AbstractPoolFactory is AbstractDependant { return address( new PublicBeaconProxy{salt: salt_}( - AbstractPoolContractsRegistry(poolRegistry_).getProxyBeacon(poolType_), + APoolContractsRegistry(poolRegistry_).getProxyBeacon(poolType_), bytes("") ) ); @@ -76,7 +76,7 @@ abstract contract AbstractPoolFactory is AbstractDependant { string memory poolType_, address poolProxy_ ) internal virtual { - AbstractPoolContractsRegistry(poolRegistry_).addProxyPool(poolType_, poolProxy_); + APoolContractsRegistry(poolRegistry_).addProxyPool(poolType_, poolProxy_); } /** @@ -84,8 +84,8 @@ abstract contract AbstractPoolFactory is AbstractDependant { * provided PoolContractsRegistry as an injector */ function _injectDependencies(address poolRegistry_, address proxy_) internal virtual { - AbstractDependant(proxy_).setDependencies(_contractsRegistry, bytes("")); - AbstractDependant(proxy_).setInjector(poolRegistry_); + ADependant(proxy_).setDependencies(_contractsRegistry, bytes("")); + ADependant(proxy_).setInjector(poolRegistry_); } /** @@ -100,7 +100,7 @@ abstract contract AbstractPoolFactory is AbstractDependant { abi.encodePacked( type(PublicBeaconProxy).creationCode, abi.encode( - AbstractPoolContractsRegistry(poolRegistry_).getProxyBeacon(poolType_), + APoolContractsRegistry(poolRegistry_).getProxyBeacon(poolType_), bytes("") ) ) diff --git a/contracts/contracts-registry/pools/presets/MultiOwnablePoolContractsRegistry.sol b/contracts/contracts-registry/pools/presets/AMultiOwnablePoolContractsRegistry.sol similarity index 80% rename from contracts/contracts-registry/pools/presets/MultiOwnablePoolContractsRegistry.sol rename to contracts/contracts-registry/pools/presets/AMultiOwnablePoolContractsRegistry.sol index c3b08688..787fd6fb 100644 --- a/contracts/contracts-registry/pools/presets/MultiOwnablePoolContractsRegistry.sol +++ b/contracts/contracts-registry/pools/presets/AMultiOwnablePoolContractsRegistry.sol @@ -1,22 +1,19 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; -import {AbstractPoolContractsRegistry} from "../AbstractPoolContractsRegistry.sol"; -import {MultiOwnable} from "../../../access/MultiOwnable.sol"; +import {APoolContractsRegistry} from "../APoolContractsRegistry.sol"; +import {AMultiOwnable} from "../../../access/AMultiOwnable.sol"; /** * @notice The MultiOwnable preset of PoolContractsRegistry */ -abstract contract MultiOwnablePoolContractsRegistry is - AbstractPoolContractsRegistry, - MultiOwnable -{ +abstract contract AMultiOwnablePoolContractsRegistry is APoolContractsRegistry, AMultiOwnable { /** * @notice The initialization function */ - function __MultiOwnablePoolContractsRegistry_init() public initializer { - __MultiOwnable_init(); - __PoolContractsRegistry_init(); + function __AMultiOwnablePoolContractsRegistry_init() public initializer { + __AMultiOwnable_init(); + __APoolContractsRegistry_init(); } /** diff --git a/contracts/contracts-registry/pools/presets/OwnablePoolContractsRegistry.sol b/contracts/contracts-registry/pools/presets/AOwnablePoolContractsRegistry.sol similarity index 84% rename from contracts/contracts-registry/pools/presets/OwnablePoolContractsRegistry.sol rename to contracts/contracts-registry/pools/presets/AOwnablePoolContractsRegistry.sol index 7d0ee0f0..1887152d 100644 --- a/contracts/contracts-registry/pools/presets/OwnablePoolContractsRegistry.sol +++ b/contracts/contracts-registry/pools/presets/AOwnablePoolContractsRegistry.sol @@ -1,23 +1,20 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import {AbstractPoolContractsRegistry} from "../AbstractPoolContractsRegistry.sol"; +import {APoolContractsRegistry} from "../APoolContractsRegistry.sol"; /** * @notice The Ownable preset of PoolContractsRegistry */ -abstract contract OwnablePoolContractsRegistry is - AbstractPoolContractsRegistry, - OwnableUpgradeable -{ +abstract contract AOwnablePoolContractsRegistry is APoolContractsRegistry, OwnableUpgradeable { /** * @notice The initialization function */ - function __OwnablePoolContractsRegistry_init() public initializer { - __Ownable_init(); - __PoolContractsRegistry_init(); + function __AOwnablePoolContractsRegistry_init() public initializer { + __Ownable_init(msg.sender); + __APoolContractsRegistry_init(); } /** diff --git a/contracts/contracts-registry/presets/MultiOwnableContractsRegistry.sol b/contracts/contracts-registry/presets/MultiOwnableContractsRegistry.sol index b4f0980d..c341f25c 100644 --- a/contracts/contracts-registry/presets/MultiOwnableContractsRegistry.sol +++ b/contracts/contracts-registry/presets/MultiOwnableContractsRegistry.sol @@ -1,19 +1,19 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.22; -import {AbstractContractsRegistry} from "../AbstractContractsRegistry.sol"; -import {MultiOwnable} from "../../access/MultiOwnable.sol"; +import {AContractsRegistry} from "../AContractsRegistry.sol"; +import {AMultiOwnable} from "../../access/AMultiOwnable.sol"; /** * @notice The MultiOwnable preset of ContractsRegistry */ -contract MultiOwnableContractsRegistry is AbstractContractsRegistry, MultiOwnable { +contract MultiOwnableContractsRegistry is AContractsRegistry, AMultiOwnable { /** * @notice The initialization function */ function __MultiOwnableContractsRegistry_init() public initializer { - __MultiOwnable_init(); - __ContractsRegistry_init(); + __AMultiOwnable_init(); + __AContractsRegistry_init(); } /** @@ -72,7 +72,7 @@ contract MultiOwnableContractsRegistry is AbstractContractsRegistry, MultiOwnabl } /** - * @notice The function to add the proxy contract to the registry (deploys TransparentProxy on top) + * @notice The function to add the proxy contract to the registry (deploys AdminableProxy on top) * @param name_ the associative name of the contract * @param contractAddress_ the address of the implementation contract to add */ @@ -81,7 +81,7 @@ contract MultiOwnableContractsRegistry is AbstractContractsRegistry, MultiOwnabl } /** - * @notice The function to add the proxy contract to the registry with immediate call (deploys TransparentProxy on top) + * @notice The function to add the proxy contract to the registry with immediate call (deploys AdminableProxy on top) * @param name_ the associative name of the contract * @param contractAddress_ the address of the implementation contract to add * @param data_ the data the proxy contract will be called after the addition diff --git a/contracts/contracts-registry/presets/OwnableContractsRegistry.sol b/contracts/contracts-registry/presets/OwnableContractsRegistry.sol index 5ce4d4d0..c092e443 100644 --- a/contracts/contracts-registry/presets/OwnableContractsRegistry.sol +++ b/contracts/contracts-registry/presets/OwnableContractsRegistry.sol @@ -1,20 +1,20 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.22; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import {AbstractContractsRegistry} from "../AbstractContractsRegistry.sol"; +import {AContractsRegistry} from "../AContractsRegistry.sol"; /** * @notice The Ownable preset of ContractsRegistry */ -contract OwnableContractsRegistry is AbstractContractsRegistry, OwnableUpgradeable { +contract OwnableContractsRegistry is AContractsRegistry, OwnableUpgradeable { /** * @notice The initialization function */ function __OwnableContractsRegistry_init() public initializer { - __Ownable_init(); - __ContractsRegistry_init(); + __Ownable_init(msg.sender); + __AContractsRegistry_init(); } /** @@ -73,7 +73,7 @@ contract OwnableContractsRegistry is AbstractContractsRegistry, OwnableUpgradeab } /** - * @notice The function to add the proxy contract to the registry (deploys TransparentProxy on top) + * @notice The function to add the proxy contract to the registry (deploys AdminableProxy on top) * @param name_ the associative name of the contract * @param contractAddress_ the address of the implementation contract to add */ @@ -82,7 +82,7 @@ contract OwnableContractsRegistry is AbstractContractsRegistry, OwnableUpgradeab } /** - * @notice The function to add the proxy contract to the registry with immediate call (deploys TransparentProxy on top) + * @notice The function to add the proxy contract to the registry with immediate call (deploys AdminableProxy on top) * @param name_ the associative name of the contract * @param contractAddress_ the address of the implementation contract to add * @param data_ the data the proxy contract will be called after the addition diff --git a/contracts/diamond/DiamondStorage.sol b/contracts/diamond/ADiamondStorage.sol similarity index 97% rename from contracts/diamond/DiamondStorage.sol rename to contracts/diamond/ADiamondStorage.sol index 4514895f..964b5b29 100644 --- a/contracts/diamond/DiamondStorage.sol +++ b/contracts/diamond/ADiamondStorage.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; @@ -8,7 +8,7 @@ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet * * This is the storage contract for the diamond proxy */ -abstract contract DiamondStorage { +abstract contract ADiamondStorage { using EnumerableSet for EnumerableSet.Bytes32Set; using EnumerableSet for EnumerableSet.AddressSet; diff --git a/contracts/diamond/Diamond.sol b/contracts/diamond/Diamond.sol index 11b1fbb8..4bb56726 100644 --- a/contracts/diamond/Diamond.sol +++ b/contracts/diamond/Diamond.sol @@ -1,10 +1,9 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; -import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {DiamondStorage} from "./DiamondStorage.sol"; +import {ADiamondStorage} from "./ADiamondStorage.sol"; /** * @notice The Diamond standard module @@ -19,8 +18,7 @@ import {DiamondStorage} from "./DiamondStorage.sol"; * * If you wish to add a receive() function, attach a "0x00000000" selector to a facet that has such a function. */ -contract Diamond is DiamondStorage { - using Address for address; +contract Diamond is ADiamondStorage { using EnumerableSet for EnumerableSet.Bytes32Set; using EnumerableSet for EnumerableSet.AddressSet; @@ -38,6 +36,16 @@ contract Diamond is DiamondStorage { event DiamondCut(Facet[] facets, address initFacet, bytes initData); + error FacetIsZeroAddress(); + error InitializationReverted(address initFacet, bytes initData); + error NoSelectorsProvided(); + error NoFacetForSelector(bytes4 selector); + + error SelectorAlreadyAdded(address faucet, bytes4 selector); + error SelectorFromAnotherFacet(bytes4 selector); + error SelectorIsAlreadyInThisFaucet(bytes4 selector, address facet); + error SelectorNotRegistered(bytes4 selector); + /** * @notice The payable fallback function that delegatecall's the facet with associated selector */ @@ -45,7 +53,7 @@ contract Diamond is DiamondStorage { fallback() external payable virtual { address facet_ = facetAddress(msg.sig); - require(facet_ != address(0), "Diamond: selector is not registered"); + if (facet_ == address(0)) revert SelectorNotRegistered(msg.sig); _beforeFallback(facet_, msg.sig); @@ -101,17 +109,13 @@ contract Diamond is DiamondStorage { * @param selectors_ the function selectors the implementation has */ function _addFacet(address facet_, bytes4[] memory selectors_) internal virtual { - require(facet_ != address(0), "Diamond: facet cannot be zero address"); - require(facet_.isContract(), "Diamond: facet is not a contract"); - require(selectors_.length != 0, "Diamond: no selectors provided"); + _checkIfFacetIsValid(facet_, selectors_); DStorage storage _ds = _getDiamondStorage(); for (uint256 i = 0; i < selectors_.length; i++) { - require( - _ds.selectorToFacet[selectors_[i]] == address(0), - "Diamond: selector already added" - ); + if (_ds.selectorToFacet[selectors_[i]] != address(0)) + revert SelectorAlreadyAdded(_ds.selectorToFacet[selectors_[i]], selectors_[i]); _ds.selectorToFacet[selectors_[i]] = facet_; _ds.facetToSelectors[facet_].add(bytes32(selectors_[i])); @@ -126,16 +130,13 @@ contract Diamond is DiamondStorage { * @param selectors_ the selectors of that implementation to be removed */ function _removeFacet(address facet_, bytes4[] memory selectors_) internal virtual { - require(facet_ != address(0), "Diamond: facet cannot be zero address"); - require(selectors_.length != 0, "Diamond: no selectors provided"); + _checkIfFacetIsValid(facet_, selectors_); DStorage storage _ds = _getDiamondStorage(); for (uint256 i = 0; i < selectors_.length; i++) { - require( - _ds.selectorToFacet[selectors_[i]] == facet_, - "Diamond: selector from another facet" - ); + if (_ds.selectorToFacet[selectors_[i]] != facet_) + revert SelectorFromAnotherFacet(selectors_[i]); _ds.selectorToFacet[selectors_[i]] = address(0); _ds.facetToSelectors[facet_].remove(bytes32(selectors_[i])); @@ -152,9 +153,7 @@ contract Diamond is DiamondStorage { * @param selectors_ the selectors of the facet */ function _updateFacet(address facet_, bytes4[] memory selectors_) internal virtual { - require(facet_ != address(0), "Diamond: facet cannot be zero address"); - require(facet_.isContract(), "Diamond: facet is not a contract"); - require(selectors_.length != 0, "Diamond: no selectors provided"); + _checkIfFacetIsValid(facet_, selectors_); DStorage storage _ds = _getDiamondStorage(); @@ -162,8 +161,8 @@ contract Diamond is DiamondStorage { bytes4 selector_ = selectors_[i]; address oldFacet_ = facetAddress(selector_); - require(oldFacet_ != facet_, "Diamond: cannot replace to the same facet"); - require(oldFacet_ != address(0), "Diamond: no facet found for selector"); + if (oldFacet_ == facet_) revert SelectorIsAlreadyInThisFaucet(selector_, facet_); + if (oldFacet_ == address(0)) revert NoFacetForSelector(selector_); // replace old facet address _ds.selectorToFacet[selector_] = facet_; @@ -190,12 +189,12 @@ contract Diamond is DiamondStorage { return; } - require(initFacet_.isContract(), "Diamond: init_ address has no code"); - + // solhint-disable-next-line (bool success_, bytes memory err_) = initFacet_.delegatecall(initData_); if (!success_) { - require(err_.length > 0, "Diamond: initialization function reverted"); + if (err_.length == 0) revert InitializationReverted(initFacet_, initData_); + // bubble up error // @solidity memory-safe-assembly assembly { @@ -204,5 +203,13 @@ contract Diamond is DiamondStorage { } } + function _checkIfFacetIsValid( + address facet_, + bytes4[] memory selectors_ + ) internal pure virtual { + if (facet_ == address(0)) revert FacetIsZeroAddress(); + if (selectors_.length == 0) revert NoSelectorsProvided(); + } + function _beforeFallback(address facet_, bytes4 selector_) internal virtual {} } diff --git a/contracts/diamond/access/access-control/DiamondAccessControl.sol b/contracts/diamond/access/access-control/ADiamondAccessControl.sol similarity index 77% rename from contracts/diamond/access/access-control/DiamondAccessControl.sol rename to contracts/diamond/access/access-control/ADiamondAccessControl.sol index 09c0ac26..3f99b8eb 100644 --- a/contracts/diamond/access/access-control/DiamondAccessControl.sol +++ b/contracts/diamond/access/access-control/ADiamondAccessControl.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; -import {DiamondAccessControlStorage, IAccessControl} from "./DiamondAccessControlStorage.sol"; +// solhint-disable-next-line no-unused-import +import {ADiamondAccessControlStorage, IAccessControl} from "./ADiamondAccessControlStorage.sol"; /** * @notice The Diamond standard module @@ -9,11 +10,14 @@ import {DiamondAccessControlStorage, IAccessControl} from "./DiamondAccessContro * This is modified version of OpenZeppelin's AccessControl contract to be used as a Storage contract * by the Diamond Standard. */ -abstract contract DiamondAccessControl is DiamondAccessControlStorage { +abstract contract ADiamondAccessControl is ADiamondAccessControlStorage { + error UnauthorizedAccount(address account); + error RoleAlreadyGranted(bytes32 role, address account); + /** * @notice Sets `DEFAULT_ADMIN_ROLE` to `msg.sender` */ - function __DiamondAccessControl_init() + function __ADiamondAccessControl_init() internal onlyInitializing(DIAMOND_ACCESS_CONTROL_STORAGE_SLOT) { @@ -44,7 +48,7 @@ abstract contract DiamondAccessControl is DiamondAccessControlStorage { * @inheritdoc IAccessControl */ function renounceRole(bytes32 role_, address account_) public virtual override { - require(account_ == msg.sender, "AccessControl: can only renounce roles for self"); + if (account_ != msg.sender) revert UnauthorizedAccount(msg.sender); _revokeRole(role_, account_); } @@ -70,7 +74,7 @@ abstract contract DiamondAccessControl is DiamondAccessControlStorage { * May emit a {RoleGranted} event. */ function _grantRole(bytes32 role_, address account_) internal virtual { - require(!hasRole(role_, account_), "AccessControl: role is granted"); + if (hasRole(role_, account_)) revert RoleAlreadyGranted(role_, account_); _getAccessControlStorage().roles[role_].members[account_] = true; @@ -85,7 +89,7 @@ abstract contract DiamondAccessControl is DiamondAccessControlStorage { * May emit a {RoleRevoked} event. */ function _revokeRole(bytes32 role_, address account_) internal virtual { - require(hasRole(role_, account_), "AccessControl: role is not granted"); + if (!hasRole(role_, account_)) revert RoleNotGranted(role_, account_); _getAccessControlStorage().roles[role_].members[account_] = false; diff --git a/contracts/diamond/access/access-control/DiamondAccessControlStorage.sol b/contracts/diamond/access/access-control/ADiamondAccessControlStorage.sol similarity index 57% rename from contracts/diamond/access/access-control/DiamondAccessControlStorage.sol rename to contracts/diamond/access/access-control/ADiamondAccessControlStorage.sol index c3b4fa71..c308f960 100644 --- a/contracts/diamond/access/access-control/DiamondAccessControlStorage.sol +++ b/contracts/diamond/access/access-control/ADiamondAccessControlStorage.sol @@ -1,17 +1,16 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; -import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import {InitializableStorage} from "../../utils/InitializableStorage.sol"; +import {AInitializableStorage} from "../../utils/AInitializableStorage.sol"; /** * @notice The Diamond standard module * * This is an AccessControl Storage contract with Diamond Standard support */ -abstract contract DiamondAccessControlStorage is IAccessControl, InitializableStorage { +abstract contract ADiamondAccessControlStorage is IAccessControl, AInitializableStorage { bytes32 public constant DIAMOND_ACCESS_CONTROL_STORAGE_SLOT = keccak256("diamond.standard.diamond.access.control.storage"); @@ -26,13 +25,11 @@ abstract contract DiamondAccessControlStorage is IAccessControl, InitializableSt mapping(bytes32 => RoleData) roles; } + error RoleNotGranted(bytes32 role, address account); + /** * @dev Modifier that checks that an account has a specific role. Reverts - * with a standardized message including the required role. - * - * The format of the revert reason is given by the following regular expression: - * - * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/ + * with a custom error including the required role. */ modifier onlyRole(bytes32 role_) { _checkRole(role_); @@ -62,34 +59,17 @@ abstract contract DiamondAccessControlStorage is IAccessControl, InitializableSt } /** - * @dev Revert with a standard message if `_msgSender()` is missing `role`. + * @dev Revert with a custom error if `_msgSender()` is missing `role`. * Overriding this function changes the behavior of the {onlyRole} modifier. - * - * Format of the revert message is described in {_checkRole}. */ function _checkRole(bytes32 role_) internal view virtual { _checkRole(role_, msg.sender); } /** - * @dev Revert with a standard message if `account` is missing `role`. - * - * The format of the revert reason is given by the following regular expression: - * - * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/ + * @dev Revert with a custom error if `account` is missing `role`. */ function _checkRole(bytes32 role_, address account_) internal view virtual { - if (!hasRole(role_, account_)) { - revert( - string( - abi.encodePacked( - "AccessControl: account ", - Strings.toHexString(account_), - " is missing role ", - Strings.toHexString(uint256(role_), 32) - ) - ) - ); - } + if (!hasRole(role_, account_)) revert RoleNotGranted(role_, account_); } } diff --git a/contracts/diamond/access/ownable/DiamondOwnableStorage.sol b/contracts/diamond/access/ownable/ADiamondOwnableStorage.sol similarity index 76% rename from contracts/diamond/access/ownable/DiamondOwnableStorage.sol rename to contracts/diamond/access/ownable/ADiamondOwnableStorage.sol index 0f29a371..12c271a2 100644 --- a/contracts/diamond/access/ownable/DiamondOwnableStorage.sol +++ b/contracts/diamond/access/ownable/ADiamondOwnableStorage.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; -import {InitializableStorage} from "../../utils/InitializableStorage.sol"; +import {AInitializableStorage} from "../../utils/AInitializableStorage.sol"; /** * @notice The Diamond standard module * * This is an Ownable Storage contract with Diamond Standard support */ -abstract contract DiamondOwnableStorage is InitializableStorage { +abstract contract ADiamondOwnableStorage is AInitializableStorage { bytes32 public constant DIAMOND_OWNABLE_STORAGE_SLOT = keccak256("diamond.standard.diamond.ownable.storage"); @@ -16,6 +16,8 @@ abstract contract DiamondOwnableStorage is InitializableStorage { address owner; } + error CallerNotOwner(address caller, address owner); + modifier onlyOwner() { _onlyOwner(); _; @@ -41,6 +43,6 @@ abstract contract DiamondOwnableStorage is InitializableStorage { * @notice The function to check if `msg.sender` is the owner */ function _onlyOwner() internal view virtual { - require(owner() == msg.sender, "DiamondOwnable: not an owner"); + if (owner() != msg.sender) revert CallerNotOwner(msg.sender, owner()); } } diff --git a/contracts/diamond/access/ownable/DiamondOwnable.sol b/contracts/diamond/access/ownable/DiamondOwnable.sol index b86113b7..0b86a37d 100644 --- a/contracts/diamond/access/ownable/DiamondOwnable.sol +++ b/contracts/diamond/access/ownable/DiamondOwnable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; -import {DiamondOwnableStorage} from "./DiamondOwnableStorage.sol"; +import {ADiamondOwnableStorage} from "./ADiamondOwnableStorage.sol"; /** * @notice The Diamond standard module @@ -9,9 +9,11 @@ import {DiamondOwnableStorage} from "./DiamondOwnableStorage.sol"; * This is modified version of OpenZeppelin's Ownable contract to be used as a Storage contract * by the Diamond Standard. */ -contract DiamondOwnable is DiamondOwnableStorage { +contract DiamondOwnable is ADiamondOwnableStorage { event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + error InvalidOwner(); + /** * @notice Transfers ownership to `msg.sender` */ @@ -24,7 +26,7 @@ contract DiamondOwnable is DiamondOwnableStorage { * @param newOwner_ the new owner of the Diamond */ function transferOwnership(address newOwner_) public virtual onlyOwner { - require(newOwner_ != address(0), "DiamondOwnable: zero address owner"); + if (newOwner_ == address(0)) revert InvalidOwner(); _transferOwnership(newOwner_); } diff --git a/contracts/diamond/introspection/DiamondERC165.sol b/contracts/diamond/introspection/DiamondERC165.sol index 6504d74a..f19d1aeb 100644 --- a/contracts/diamond/introspection/DiamondERC165.sol +++ b/contracts/diamond/introspection/DiamondERC165.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; diff --git a/contracts/diamond/presets/OwnableDiamond.sol b/contracts/diamond/presets/OwnableDiamond.sol index 59b479a2..3f0dc44f 100644 --- a/contracts/diamond/presets/OwnableDiamond.sol +++ b/contracts/diamond/presets/OwnableDiamond.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {Diamond} from "../Diamond.sol"; import {DiamondOwnable} from "../access/ownable/DiamondOwnable.sol"; diff --git a/contracts/diamond/tokens/ERC20/DiamondERC20Storage.sol b/contracts/diamond/tokens/ERC20/ADiamondERC20Storage.sol similarity index 92% rename from contracts/diamond/tokens/ERC20/DiamondERC20Storage.sol rename to contracts/diamond/tokens/ERC20/ADiamondERC20Storage.sol index e52df914..778b5f99 100644 --- a/contracts/diamond/tokens/ERC20/DiamondERC20Storage.sol +++ b/contracts/diamond/tokens/ERC20/ADiamondERC20Storage.sol @@ -1,17 +1,17 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import {InitializableStorage} from "../../utils/InitializableStorage.sol"; +import {AInitializableStorage} from "../../utils/AInitializableStorage.sol"; /** * @notice The Diamond standard module * * This is an ERC20 token Storage contract with Diamond Standard support */ -abstract contract DiamondERC20Storage is InitializableStorage, IERC20, IERC20Metadata { +abstract contract ADiamondERC20Storage is AInitializableStorage, IERC20, IERC20Metadata { bytes32 public constant DIAMOND_ERC20_STORAGE_SLOT = keccak256("diamond.standard.diamond.erc20.storage"); diff --git a/contracts/diamond/tokens/ERC20/DiamondERC20.sol b/contracts/diamond/tokens/ERC20/DiamondERC20.sol index 5e3115c1..a8d83209 100644 --- a/contracts/diamond/tokens/ERC20/DiamondERC20.sol +++ b/contracts/diamond/tokens/ERC20/DiamondERC20.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; +// solhint-disable-next-line no-unused-import import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {DiamondERC20Storage} from "./DiamondERC20Storage.sol"; +import {ADiamondERC20Storage} from "./ADiamondERC20Storage.sol"; /** * @notice The Diamond standard module @@ -11,7 +12,14 @@ import {DiamondERC20Storage} from "./DiamondERC20Storage.sol"; * This is modified version of OpenZeppelin's ERC20 contract to be used as a Storage contract * by the Diamond Standard. */ -contract DiamondERC20 is DiamondERC20Storage { +contract DiamondERC20 is ADiamondERC20Storage { + error ApproverIsZeroAddress(); + error InsufficientAllowance(address spender, uint256 allowance, uint256 needed); + error InsufficientBalance(address sender, uint256 balance, uint256 needed); + error ReceiverIsZeroAddress(); + error SenderIsZeroAddress(); + error SpenderIsZeroAddress(); + /** * @notice Sets the values for {name} and {symbol}. * @@ -61,86 +69,81 @@ contract DiamondERC20 is DiamondERC20Storage { /** * @notice Moves `amount` of tokens from `from` to `to`. + * @dev This function is not virtual, {_update} should be overridden instead. */ - function _transfer(address from_, address to_, uint256 amount_) internal virtual { - require(from_ != address(0), "ERC20: transfer from the zero address"); - require(to_ != address(0), "ERC20: transfer to the zero address"); - - _beforeTokenTransfer(from_, to_, amount_); - - DERC20Storage storage _erc20Storage = _getErc20Storage(); - - uint256 fromBalance_ = _erc20Storage.balances[from_]; - - require(fromBalance_ >= amount_, "ERC20: transfer amount exceeds balance"); - - unchecked { - _erc20Storage.balances[from_] = fromBalance_ - amount_; - - // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by - // decrementing then incrementing. - _erc20Storage.balances[to_] += amount_; - } + function _transfer(address from_, address to_, uint256 amount_) internal { + if (from_ == address(0)) revert SenderIsZeroAddress(); + if (to_ == address(0)) revert ReceiverIsZeroAddress(); - emit Transfer(from_, to_, amount_); - - _afterTokenTransfer(from_, to_, amount_); + _update(from_, to_, amount_); } /** * @notice Creates `amount` tokens and assigns them to `account`, increasing * the total supply. + * @dev This function is not virtual, {_update} should be overridden instead. */ - function _mint(address account_, uint256 amount_) internal virtual { - require(account_ != address(0), "ERC20: mint to the zero address"); - - _beforeTokenTransfer(address(0), account_, amount_); - - DERC20Storage storage _erc20Storage = _getErc20Storage(); - - _erc20Storage.totalSupply += amount_; - - unchecked { - // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above. - _erc20Storage.balances[account_] += amount_; - } - - emit Transfer(address(0), account_, amount_); + function _mint(address account_, uint256 amount_) internal { + if (account_ == address(0)) revert ReceiverIsZeroAddress(); - _afterTokenTransfer(address(0), account_, amount_); + _update(address(0), account_, amount_); } /** * @notice Destroys `amount` tokens from `account`, reducing the * total supply. + * @dev This function is not virtual, {_update} should be overridden instead. */ - function _burn(address account_, uint256 amount_) internal virtual { - require(account_ != address(0), "ERC20: burn from the zero address"); + function _burn(address account_, uint256 amount_) internal { + if (account_ == address(0)) revert SenderIsZeroAddress(); - _beforeTokenTransfer(account_, address(0), amount_); + _update(account_, address(0), amount_); + } + /** + * @dev Transfers a `amount` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from` + * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding + * this function. + * Emits a {Transfer} event. + */ + function _update(address from_, address to_, uint256 amount_) internal virtual { DERC20Storage storage _erc20Storage = _getErc20Storage(); - uint256 accountBalance_ = _erc20Storage.balances[account_]; - require(accountBalance_ >= amount_, "ERC20: burn amount exceeds balance"); + if (from_ == address(0)) { + // Overflow check required: The rest of the code assumes that totalSupply never overflows + _erc20Storage.totalSupply += amount_; + } else { + uint256 fromBalance_ = _erc20Storage.balances[from_]; - unchecked { - _erc20Storage.balances[account_] -= amount_; - // Overflow not possible: amount <= accountBalance <= totalSupply. - _erc20Storage.totalSupply -= amount_; + if (fromBalance_ < amount_) revert InsufficientBalance(from_, fromBalance_, amount_); + + unchecked { + // Overflow not possible: amount <= fromBalance <= totalSupply. + _erc20Storage.balances[from_] = fromBalance_ - amount_; + } } - emit Transfer(account_, address(0), amount_); + if (to_ == address(0)) { + unchecked { + // Overflow not possible: amount <= fromBalance <= totalSupply. + _erc20Storage.totalSupply -= amount_; + } + } else { + unchecked { + // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above. + _erc20Storage.balances[to_] += amount_; + } + } - _afterTokenTransfer(account_, address(0), amount_); + emit Transfer(from_, to_, amount_); } /** * @notice Sets `amount` as the allowance of `spender` over the `owner` s tokens. */ function _approve(address owner_, address spender_, uint256 amount_) internal virtual { - require(owner_ != address(0), "ERC20: approve from the zero address"); - require(spender_ != address(0), "ERC20: approve to the zero address"); + if (owner_ == address(0)) revert ApproverIsZeroAddress(); + if (spender_ == address(0)) revert SpenderIsZeroAddress(); _getErc20Storage().allowances[owner_][spender_] = amount_; @@ -154,23 +157,12 @@ contract DiamondERC20 is DiamondERC20Storage { uint256 currentAllowance_ = allowance(owner_, spender_); if (currentAllowance_ != type(uint256).max) { - require(currentAllowance_ >= amount_, "ERC20: insufficient allowance"); + if (currentAllowance_ < amount_) + revert InsufficientAllowance(spender_, currentAllowance_, amount_); unchecked { _approve(owner_, spender_, currentAllowance_ - amount_); } } } - - /** - * @notice Hook that is called before any transfer of tokens. This includes - * minting and burning. - */ - function _beforeTokenTransfer(address from_, address to_, uint256 amount_) internal virtual {} - - /** - * @notice Hook that is called after any transfer of tokens. This includes - * minting and burning. - */ - function _afterTokenTransfer(address from_, address to_, uint256 amount_) internal virtual {} } diff --git a/contracts/diamond/tokens/ERC721/DiamondERC721Storage.sol b/contracts/diamond/tokens/ERC721/ADiamondERC721Storage.sol similarity index 90% rename from contracts/diamond/tokens/ERC721/DiamondERC721Storage.sol rename to contracts/diamond/tokens/ERC721/ADiamondERC721Storage.sol index 6ba7c047..4838a914 100644 --- a/contracts/diamond/tokens/ERC721/DiamondERC721Storage.sol +++ b/contracts/diamond/tokens/ERC721/ADiamondERC721Storage.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; @@ -8,15 +8,15 @@ import {IERC721Metadata} from "@openzeppelin/contracts/token/ERC721/extensions/I import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {DiamondERC165} from "../../introspection/DiamondERC165.sol"; -import {InitializableStorage} from "../../utils/InitializableStorage.sol"; +import {AInitializableStorage} from "../../utils/AInitializableStorage.sol"; /** * @notice The Diamond standard module * * This is an ERC721 token Storage contract with Diamond Standard support */ -abstract contract DiamondERC721Storage is - InitializableStorage, +abstract contract ADiamondERC721Storage is + AInitializableStorage, DiamondERC165, IERC721, IERC721Metadata @@ -39,6 +39,10 @@ abstract contract DiamondERC721Storage is mapping(address => mapping(uint256 => uint256)) ownedTokens; } + error IndexOutOfBounds(uint256 index); + error NonexistentToken(uint256 tokenId); + error OwnerIndexOutOfBounds(address owner, uint256 index); + function _getErc721Storage() internal pure returns (DERC721Storage storage _erc721Storage) { bytes32 slot_ = DIAMOND_ERC721_STORAGE_SLOT; @@ -113,7 +117,7 @@ abstract contract DiamondERC721Storage is address owner_, uint256 index_ ) public view virtual returns (uint256) { - require(index_ < balanceOf(owner_), "ERC721Enumerable: owner index out of bounds"); + if (index_ >= balanceOf(owner_)) revert OwnerIndexOutOfBounds(owner_, index_); return _getErc721Storage().ownedTokens[owner_][index_]; } @@ -122,7 +126,7 @@ abstract contract DiamondERC721Storage is * @notice This function allows you to retrieve the NFT token ID at a given `index` of all the tokens stored by the contract. */ function tokenByIndex(uint256 index_) public view virtual returns (uint256) { - require(index_ < totalSupply(), "ERC721Enumerable: global index out of bounds"); + if (index_ >= totalSupply()) revert IndexOutOfBounds(index_); return _getErc721Storage().allTokens[index_]; } @@ -133,7 +137,7 @@ abstract contract DiamondERC721Storage is function ownerOf(uint256 tokenId_) public view virtual override returns (address) { address owner = _ownerOf(tokenId_); - require(owner != address(0), "ERC721: invalid token ID"); + if (owner == address(0)) revert NonexistentToken(tokenId_); return owner; } @@ -168,7 +172,7 @@ abstract contract DiamondERC721Storage is * @notice The function that reverts if the `tokenId` has not been minted yet. */ function _requireMinted(uint256 tokenId_) internal view virtual { - require(_exists(tokenId_), "ERC721: invalid token ID"); + if (!_exists(tokenId_)) revert NonexistentToken(tokenId_); } /** diff --git a/contracts/diamond/tokens/ERC721/DiamondERC721.sol b/contracts/diamond/tokens/ERC721/DiamondERC721.sol index 148138c3..4239a61c 100644 --- a/contracts/diamond/tokens/ERC721/DiamondERC721.sol +++ b/contracts/diamond/tokens/ERC721/DiamondERC721.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; +// solhint-disable-next-line no-unused-import import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; -import {Address} from "@openzeppelin/contracts/utils/Address.sol"; -import {DiamondERC721Storage} from "./DiamondERC721Storage.sol"; +import {ADiamondERC721Storage} from "./ADiamondERC721Storage.sol"; /** * @notice The Diamond standard module @@ -13,8 +13,16 @@ import {DiamondERC721Storage} from "./DiamondERC721Storage.sol"; * This is modified version of OpenZeppelin's ERC721 contract to be used as a Storage contract * by the Diamond Standard. */ -contract DiamondERC721 is DiamondERC721Storage { - using Address for address; +contract DiamondERC721 is ADiamondERC721Storage { + error ApproveToCaller(address caller); + error ApprovalToCurrentOwner(address owner, uint256 tokenId); + error ConsecutiveTransfersNotSupported(); + error InvalidApprover(address approver, address owner); + error InvalidSpender(address spender, uint256 tokenId); + error NonERC721Receiver(address receiver); + error ReceiverIsZeroAddress(); + error TokenAlreadyMinted(uint256 tokenId); + error UnauthorizedAccount(address account); /** * @notice Sets the values for {name} and {symbol}. @@ -34,12 +42,10 @@ contract DiamondERC721 is DiamondERC721Storage { */ function approve(address to_, uint256 tokenId_) public virtual override { address owner_ = ownerOf(tokenId_); - require(to_ != owner_, "ERC721: approval to current owner"); - require( - msg.sender == owner_ || isApprovedForAll(owner_, msg.sender), - "ERC721: approve caller is not token owner or approved for all" - ); + if (to_ == owner_) revert ApprovalToCurrentOwner(owner_, tokenId_); + if (msg.sender != owner_ && !isApprovedForAll(owner_, msg.sender)) + revert InvalidApprover(msg.sender, owner_); _approve(to_, tokenId_); } @@ -55,10 +61,7 @@ contract DiamondERC721 is DiamondERC721Storage { * @inheritdoc IERC721 */ function transferFrom(address from_, address to_, uint256 tokenId_) public virtual override { - require( - _isApprovedOrOwner(msg.sender, tokenId_), - "ERC721: caller is not token owner or approved" - ); + if (!_isApprovedOrOwner(msg.sender, tokenId_)) revert InvalidSpender(msg.sender, tokenId_); _transfer(from_, to_, tokenId_); } @@ -83,10 +86,7 @@ contract DiamondERC721 is DiamondERC721Storage { uint256 tokenId_, bytes memory data_ ) public virtual override { - require( - _isApprovedOrOwner(msg.sender, tokenId_), - "ERC721: caller is not token owner or approved" - ); + if (!_isApprovedOrOwner(msg.sender, tokenId_)) revert InvalidSpender(msg.sender, tokenId_); _safeTransfer(from_, to_, tokenId_, data_); } @@ -103,10 +103,7 @@ contract DiamondERC721 is DiamondERC721Storage { ) internal virtual { _transfer(from_, to_, tokenId_); - require( - _checkOnERC721Received(from_, to_, tokenId_, data_), - "ERC721: transfer to non ERC721Receiver implementer" - ); + if (!_checkOnERC721Received(from_, to_, tokenId_, data_)) revert NonERC721Receiver(to_); } /** @@ -122,91 +119,99 @@ contract DiamondERC721 is DiamondERC721Storage { function _safeMint(address to_, uint256 tokenId_, bytes memory data_) internal virtual { _mint(to_, tokenId_); - require( - _checkOnERC721Received(address(0), to_, tokenId_, data_), - "ERC721: transfer to non ERC721Receiver implementer" - ); + if (!_checkOnERC721Received(address(0), to_, tokenId_, data_)) + revert NonERC721Receiver(to_); } /** * @notice Mints `tokenId` and transfers it to `to`. + * @dev This function is not virtual, {_update} should be overridden instead. */ - function _mint(address to_, uint256 tokenId_) internal virtual { - require(to_ != address(0), "ERC721: mint to the zero address"); - require(!_exists(tokenId_), "ERC721: token already minted"); + function _mint(address to_, uint256 tokenId_) internal { + if (to_ == address(0)) revert ReceiverIsZeroAddress(); + if (_exists(tokenId_)) revert TokenAlreadyMinted(tokenId_); - _beforeTokenTransfer(address(0), to_, tokenId_, 1); + address previousOwner_ = _update(to_, tokenId_, 1); - // Check that tokenId was not minted by `_beforeTokenTransfer` hook - require(!_exists(tokenId_), "ERC721: token already minted"); - - DERC721Storage storage _erc721Storage = _getErc721Storage(); - - unchecked { - _erc721Storage.balances[to_] += 1; - } - - _erc721Storage.owners[tokenId_] = to_; - - emit Transfer(address(0), to_, tokenId_); - - _afterTokenTransfer(address(0), to_, tokenId_, 1); + if (previousOwner_ != address(0)) revert TokenAlreadyMinted(tokenId_); } /** * @notice Destroys `tokenId`. + * @dev This function is not virtual, {_update} should be overridden instead. */ - function _burn(uint256 tokenId_) internal virtual { - address owner_ = ownerOf(tokenId_); - - _beforeTokenTransfer(owner_, address(0), tokenId_, 1); + function _burn(uint256 tokenId_) internal { + address owner_ = _update(address(0), tokenId_, 1); - // Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook - owner_ = ownerOf(tokenId_); + if (owner_ == address(0)) revert NonexistentToken(tokenId_); + } - DERC721Storage storage _erc721Storage = _getErc721Storage(); + /** + * @notice Transfers `tokenId` from `from` to `to`. + * @dev This function is not virtual, {_update} should be overridden instead. + */ + function _transfer(address from_, address to_, uint256 tokenId_) internal { + if (to_ == address(0)) revert ReceiverIsZeroAddress(); - // Clear approvals - delete _erc721Storage.tokenApprovals[tokenId_]; + address previousOwner_ = _update(to_, tokenId_, 1); - unchecked { - _erc721Storage.balances[owner_] -= 1; + if (previousOwner_ == address(0)) { + revert NonexistentToken(tokenId_); + } else if (previousOwner_ != from_) { + revert UnauthorizedAccount(from_); } - - delete _erc721Storage.owners[tokenId_]; - - emit Transfer(owner_, address(0), tokenId_); - - _afterTokenTransfer(owner_, address(0), tokenId_, 1); } /** - * @notice Transfers `tokenId` from `from` to `to`. + * @notice Transfers `tokenId` from its current owner to `to`, or alternatively mints (or burns) if the + * current owner (or `to`) is the zero address. Returns the owner of the `tokenId` before the update. + * Emits a {Transfer} event. */ - function _transfer(address from_, address to_, uint256 tokenId_) internal virtual { - require(ownerOf(tokenId_) == from_, "ERC721: transfer from incorrect owner"); - require(to_ != address(0), "ERC721: transfer to the zero address"); + function _update( + address to_, + uint256 tokenId_, + uint256 batchSize_ + ) internal virtual returns (address) { + if (batchSize_ > 1) { + // Will only trigger during construction. Batch transferring (minting) is not available afterwards. + revert ConsecutiveTransfersNotSupported(); + } - _beforeTokenTransfer(from_, to_, tokenId_, 1); + DERC721Storage storage _erc721Storage = _getErc721Storage(); - // Check that tokenId was not transferred by `_beforeTokenTransfer` hook - require(ownerOf(tokenId_) == from_, "ERC721: transfer from incorrect owner"); + address from_ = _ownerOf(tokenId_); - DERC721Storage storage _erc721Storage = _getErc721Storage(); + if (from_ == address(0)) { + _addTokenToAllTokensEnumeration(tokenId_); + } else { + if (from_ != to_) { + _removeTokenFromOwnerEnumeration(from_, tokenId_); + } - // Clear approvals from the previous owner - delete _erc721Storage.tokenApprovals[tokenId_]; + delete _erc721Storage.tokenApprovals[tokenId_]; - unchecked { - _erc721Storage.balances[from_] -= 1; - _erc721Storage.balances[to_] += 1; + unchecked { + _erc721Storage.balances[from_] -= 1; + } + } + + if (to_ == address(0)) { + _removeTokenFromAllTokensEnumeration(tokenId_); + } else { + if (to_ != from_) { + _addTokenToOwnerEnumeration(to_, tokenId_); + } + + unchecked { + _erc721Storage.balances[to_] += 1; + } } _getErc721Storage().owners[tokenId_] = to_; emit Transfer(from_, to_, tokenId_); - _afterTokenTransfer(from_, to_, tokenId_, 1); + return from_; } /** @@ -226,7 +231,7 @@ contract DiamondERC721 is DiamondERC721Storage { address operator_, bool approved_ ) internal virtual { - require(owner_ != operator_, "ERC721: approve to caller"); + if (owner_ == operator_) revert ApproveToCaller(owner_); _getErc721Storage().operatorApprovals[owner_][operator_] = approved_; @@ -242,15 +247,15 @@ contract DiamondERC721 is DiamondERC721Storage { address to_, uint256 tokenId_, bytes memory data_ - ) private returns (bool) { - if (to_.isContract()) { + ) internal virtual returns (bool) { + if (to_.code.length > 0) { try IERC721Receiver(to_).onERC721Received(msg.sender, from_, tokenId_, data_) returns ( bytes4 retval ) { return retval == IERC721Receiver.onERC721Received.selector; } catch (bytes memory reason) { if (reason.length == 0) { - revert("ERC721: transfer to non ERC721Receiver implementer"); + revert NonERC721Receiver(to_); } else { // @solidity memory-safe-assembly assembly { @@ -263,45 +268,6 @@ contract DiamondERC721 is DiamondERC721Storage { } } - /** - * @notice Hook that is called before any token transfer. This includes minting and burning. - */ - function _beforeTokenTransfer( - address from_, - address to_, - uint256 firstTokenId_, - uint256 batchSize_ - ) internal virtual { - if (batchSize_ > 1) { - // Will only trigger during construction. Batch transferring (minting) is not available afterwards. - revert("ERC721Enumerable: consecutive transfers not supported"); - } - - uint256 tokenId_ = firstTokenId_; - - if (from_ == address(0)) { - _addTokenToAllTokensEnumeration(tokenId_); - } else if (from_ != to_) { - _removeTokenFromOwnerEnumeration(from_, tokenId_); - } - - if (to_ == address(0)) { - _removeTokenFromAllTokensEnumeration(tokenId_); - } else if (to_ != from_) { - _addTokenToOwnerEnumeration(to_, tokenId_); - } - } - - /** - * @notice Hook that is called after any token transfer. This includes minting and burning. - */ - function _afterTokenTransfer( - address from_, - address to_, - uint256 firstTokenId_, - uint256 batchSize_ - ) internal virtual {} - /** * @notice Private function to add a token to ownership-tracking data structures. */ diff --git a/contracts/diamond/utils/AInitializableStorage.sol b/contracts/diamond/utils/AInitializableStorage.sol new file mode 100644 index 00000000..39613678 --- /dev/null +++ b/contracts/diamond/utils/AInitializableStorage.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +/** + * @notice The Diamond standard module + * + * This is a modified version of the OpenZeppelin Initializable contract to be compatible + * with the Diamond Standard. + */ +abstract contract AInitializableStorage { + bytes32 internal constant INITIALIZABLE_STORAGE_SLOT = + keccak256("diamond.standard.initializable.storage"); + + struct IStorage { + mapping(bytes32 => InitializableStorage) initializableStorage; + } + + struct InitializableStorage { + uint64 initialized; + bool initializing; + } + + event Initialized(bytes32 storageSlot, uint64 version); + + error AlreadyInitialized(); + error InvalidInitialization(); + error NotInitializing(); + + /** + * @dev A modifier that defines a protected initializer function that can be invoked at most + * once for a particular storage in a Diamond proxy that begins with {storageSlot_}. + * In its scope, `onlyInitializing` functions can be used to initialize parent contracts. + * + * Emits an {Initialized} event. + */ + modifier initializer(bytes32 storageSlot_) { + if (_getInitializedVersion(storageSlot_) != 0) revert AlreadyInitialized(); + + _setInitializing(storageSlot_, true); + _; + _setInitializing(storageSlot_, false); + + _setInitialized(storageSlot_, 1); + + emit Initialized(storageSlot_, 1); + } + + /** + * @dev A modifier that defines a protected reinitializer function that can be invoked at most once + * for a particular storage in a Diamond proxy that begins with {storageSlot_}, + * and only if the storage hasn't been initialized to a greater version before. + * In its scope, `onlyInitializing` functions can be used to initialize parent contracts. + * + * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in + * a storage slot, executing them in the right order is up to the developer or operator. + * + * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization. + * + * Emits an {Initialized} event. + */ + modifier reinitializer(bytes32 storageSlot_, uint64 version_) { + if (_isInitializing(storageSlot_) || _getInitializedVersion(storageSlot_) >= version_) { + revert InvalidInitialization(); + } + + _setInitialized(storageSlot_, version_); + + _setInitializing(storageSlot_, true); + _; + _setInitializing(storageSlot_, false); + + emit Initialized(storageSlot_, version_); + } + + /** + * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the + * {initializer} modifier, directly or indirectly. + */ + modifier onlyInitializing(bytes32 storageSlot_) { + if (!_isInitializing(storageSlot_)) revert NotInitializing(); + _; + } + + function _getInitializableStorage() internal pure returns (IStorage storage _iss) { + bytes32 slot_ = INITIALIZABLE_STORAGE_SLOT; + + assembly { + _iss.slot := slot_ + } + } + + /** + * @dev Locks the contract. + * It is recommended to use this to lock Diamond Facets contracts. + * + * Emits an {Initialized} event the first time it is successfully executed. + */ + function _disableInitializers(bytes32 storageSlot_) internal virtual { + if (_isInitializing(storageSlot_)) revert InvalidInitialization(); + + if (_getInitializedVersion(storageSlot_) != type(uint64).max) { + _setInitialized(storageSlot_, type(uint64).max); + + emit Initialized(storageSlot_, type(uint64).max); + } + } + + /** + * @dev Returns the highest version that has been initialized for the provided storage slot. + */ + function _getInitializedVersion(bytes32 storageSlot_) internal view returns (uint64) { + return _getInitializableStorage().initializableStorage[storageSlot_].initialized; + } + + /** + * @dev Returns 1 if the storage at the specified slot is currently initializing, 0 otherwise. + */ + function _isInitializing(bytes32 storageSlot_) internal view returns (bool) { + return _getInitializableStorage().initializableStorage[storageSlot_].initializing; + } + + /** + * @dev Internal function that sets the initialization version for the provided storage slot. + */ + function _setInitialized(bytes32 storageSlot_, uint64 value_) private { + _getInitializableStorage().initializableStorage[storageSlot_].initialized = value_; + } + + /** + * @dev Internal function that sets the initializing value for the provided storage slot. + */ + function _setInitializing(bytes32 storageSlot_, bool value_) private { + _getInitializableStorage().initializableStorage[storageSlot_].initializing = value_; + } +} diff --git a/contracts/diamond/utils/InitializableStorage.sol b/contracts/diamond/utils/InitializableStorage.sol deleted file mode 100644 index fa8736f2..00000000 --- a/contracts/diamond/utils/InitializableStorage.sol +++ /dev/null @@ -1,89 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -/** - * @notice The Diamond standard module - * - * This is a modified version of the OpenZeppelin Initializable contract to be compatible - * with the Diamond Standard. - */ -abstract contract InitializableStorage { - bytes32 internal constant INITIALIZABLE_STORAGE_SLOT = - keccak256("diamond.standard.initializable.storage"); - - struct IStorage { - // storage slot => { 0: not initialized, 1: initializing, 2: initialized } - mapping(bytes32 => uint8) initializingStorage; - } - - event Initialized(bytes32 storageSlot); - - /** - * @dev A modifier that defines a protected initializer function that can be invoked at most - * once for a particular storage in a Diamond proxy that begins with {storageSlot_}. - * In its scope, `onlyInitializing` functions can be used to initialize parent contracts. - * - * Emits an {Initialized} event. - */ - modifier initializer(bytes32 storageSlot_) { - uint8 initializing_ = _getInitializing(storageSlot_); - - require(initializing_ == 0, "Initializable: contract is already initialized"); - - _setInitializing(storageSlot_, 1); - _; - _setInitializing(storageSlot_, 2); - - emit Initialized(storageSlot_); - } - - /** - * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the - * {initializer} modifier, directly or indirectly. - */ - modifier onlyInitializing(bytes32 storageSlot_) { - require( - _getInitializing(storageSlot_) == 1, - "Initializable: contract is not initializing" - ); - _; - } - - function _getInitializableStorage() internal pure returns (IStorage storage _iss) { - bytes32 slot_ = INITIALIZABLE_STORAGE_SLOT; - - assembly { - _iss.slot := slot_ - } - } - - /** - * @dev Locks the contract. - * It is recommended to use this to lock Diamond Facets contracts. - * - * Emits an {Initialized} event the first time it is successfully executed. - */ - function _disableInitializers(bytes32 storageSlot_) internal virtual { - uint8 initializing_ = _getInitializing(storageSlot_); - - require(initializing_ == 0, "Initializable: contract is initializing"); - - _setInitializing(storageSlot_, 2); - - emit Initialized(storageSlot_); - } - - /** - * @dev Internal function that returns the initializing for the specified storage slot. - */ - function _getInitializing(bytes32 storageSlot_) internal view returns (uint8) { - return _getInitializableStorage().initializingStorage[storageSlot_]; - } - - /** - * @dev Internal function that sets the initializingStorage value. - */ - function _setInitializing(bytes32 storageSlot_, uint8 value_) private { - _getInitializableStorage().initializingStorage[storageSlot_] = value_; - } -} diff --git a/contracts/finance/compound-rate-keeper/AbstractCompoundRateKeeper.sol b/contracts/finance/compound-rate-keeper/ACompoundRateKeeper.sol similarity index 93% rename from contracts/finance/compound-rate-keeper/AbstractCompoundRateKeeper.sol rename to contracts/finance/compound-rate-keeper/ACompoundRateKeeper.sol index 33af30c8..6cabc5ec 100644 --- a/contracts/finance/compound-rate-keeper/AbstractCompoundRateKeeper.sol +++ b/contracts/finance/compound-rate-keeper/ACompoundRateKeeper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; @@ -25,7 +25,7 @@ import {PRECISION} from "../../utils/Globals.sol"; * The compound rate is calculated with 10\**25 precision. * The maximal possible compound rate is (type(uint128).max * 10\**25) */ -abstract contract AbstractCompoundRateKeeper is ICompoundRateKeeper, Initializable { +abstract contract ACompoundRateKeeper is ICompoundRateKeeper, Initializable { using Math for uint256; uint256 private _capitalizationRate; @@ -37,10 +37,14 @@ abstract contract AbstractCompoundRateKeeper is ICompoundRateKeeper, Initializab uint256 private _currentRate; + error CapitalizationPeriodIsZero(); + error MaxRateIsReached(); + error RateIsLessThanOne(uint256 rate); + /** * @notice The initialization function */ - function __CompoundRateKeeper_init( + function __ACompoundRateKeeper_init( uint256 capitalizationRate_, uint64 capitalizationPeriod_ ) internal onlyInitializing { @@ -177,7 +181,7 @@ abstract contract AbstractCompoundRateKeeper is ICompoundRateKeeper, Initializab * @notice The private function to update the compound rate */ function _update() private { - require(!_isMaxRateReached, "CRK: max rate is reached"); + if (_isMaxRateReached) revert MaxRateIsReached(); _currentRate = getCompoundRate(); _lastUpdate = uint64(block.timestamp); @@ -187,7 +191,7 @@ abstract contract AbstractCompoundRateKeeper is ICompoundRateKeeper, Initializab * @notice The private function that changes to capitalization rate */ function _changeCapitalizationRate(uint256 capitalizationRate_) private { - require(capitalizationRate_ >= PRECISION, "CRK: rate is less than 1"); + if (capitalizationRate_ < PRECISION) revert RateIsLessThanOne(capitalizationRate_); _capitalizationRate = capitalizationRate_; @@ -198,7 +202,7 @@ abstract contract AbstractCompoundRateKeeper is ICompoundRateKeeper, Initializab * @notice The private function that changes to capitalization period */ function _changeCapitalizationPeriod(uint64 capitalizationPeriod_) private { - require(capitalizationPeriod_ > 0, "CRK: invalid period"); + if (capitalizationPeriod_ == 0) revert CapitalizationPeriodIsZero(); _capitalizationPeriod = capitalizationPeriod_; diff --git a/contracts/finance/compound-rate-keeper/presets/OwnableCompoundRateKeeper.sol b/contracts/finance/compound-rate-keeper/presets/OwnableCompoundRateKeeper.sol index 75655395..cf0f92bc 100644 --- a/contracts/finance/compound-rate-keeper/presets/OwnableCompoundRateKeeper.sol +++ b/contracts/finance/compound-rate-keeper/presets/OwnableCompoundRateKeeper.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import {AbstractCompoundRateKeeper} from "../AbstractCompoundRateKeeper.sol"; +import {ACompoundRateKeeper} from "../ACompoundRateKeeper.sol"; /** * @notice The Ownable preset of CompoundRateKeeper */ -contract OwnableCompoundRateKeeper is AbstractCompoundRateKeeper, OwnableUpgradeable { +contract OwnableCompoundRateKeeper is ACompoundRateKeeper, OwnableUpgradeable { /** * @notice The initialization function * @param capitalizationRate_ the compound interest rate with 10\**25 precision @@ -18,8 +18,8 @@ contract OwnableCompoundRateKeeper is AbstractCompoundRateKeeper, OwnableUpgrade uint256 capitalizationRate_, uint64 capitalizationPeriod_ ) public initializer { - __Ownable_init(); - __CompoundRateKeeper_init(capitalizationRate_, capitalizationPeriod_); + __Ownable_init(msg.sender); + __ACompoundRateKeeper_init(capitalizationRate_, capitalizationPeriod_); } /** diff --git a/contracts/finance/staking/AbstractStaking.sol b/contracts/finance/staking/AStaking.sol similarity index 91% rename from contracts/finance/staking/AbstractStaking.sol rename to contracts/finance/staking/AStaking.sol index 65480438..c55d2541 100644 --- a/contracts/finance/staking/AbstractStaking.sol +++ b/contracts/finance/staking/AStaking.sol @@ -1,18 +1,18 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import {AbstractValueDistributor} from "./AbstractValueDistributor.sol"; +import {AValueDistributor} from "./AValueDistributor.sol"; /** - * @notice The AbstractStaking module + * @notice The Staking module * * Contract module for staking tokens and earning rewards based on shares. */ -abstract contract AbstractStaking is AbstractValueDistributor, Initializable { +abstract contract AStaking is AValueDistributor, Initializable { using SafeERC20 for IERC20; address private _sharesToken; @@ -31,6 +31,10 @@ abstract contract AbstractStaking is AbstractValueDistributor, Initializable { uint256 private _stakingStartTime; + error RewardsTokenIsZeroAddress(); + error SharesTokenIsZeroAddress(); + error StakingHasNotStarted(uint256 currentTimestamp, uint256 stakingStartTime); + /** * @dev Throws if the staking has not started yet. */ @@ -50,14 +54,14 @@ abstract contract AbstractStaking is AbstractValueDistributor, Initializable { * @param rate_ The reward rate. * @param stakingStartTime_ The staking start time */ - function __AbstractStaking_init( + function __AStaking_init( address sharesToken_, address rewardsToken_, uint256 rate_, uint256 stakingStartTime_ ) internal onlyInitializing { - require(sharesToken_ != address(0), "Staking: zero address cannot be the Shares Token"); - require(rewardsToken_ != address(0), "Staking: zero address cannot be the Rewards Token"); + if (sharesToken_ == address(0)) revert SharesTokenIsZeroAddress(); + if (rewardsToken_ == address(0)) revert RewardsTokenIsZeroAddress(); _sharesToken = sharesToken_; _rewardsToken = rewardsToken_; @@ -197,7 +201,8 @@ abstract contract AbstractStaking is AbstractValueDistributor, Initializable { * @dev Throws if the staking has not started yet. */ function _checkStakingStarted() internal view { - require(block.timestamp >= _stakingStartTime, "Staking: staking has not started yet"); + if (block.timestamp < _stakingStartTime) + revert StakingHasNotStarted(block.timestamp, _stakingStartTime); } /** diff --git a/contracts/finance/staking/AbstractValueDistributor.sol b/contracts/finance/staking/AValueDistributor.sol similarity index 90% rename from contracts/finance/staking/AbstractValueDistributor.sol rename to contracts/finance/staking/AValueDistributor.sol index 9a0436f6..35211b2d 100644 --- a/contracts/finance/staking/AbstractValueDistributor.sol +++ b/contracts/finance/staking/AValueDistributor.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {PRECISION} from "../../utils/Globals.sol"; /** - * @notice The AbstractValueDistributor module + * @notice The Staking module * * Contract module for distributing value among users based on their shares. * @@ -17,7 +17,7 @@ import {PRECISION} from "../../utils/Globals.sol"; * It includes hooks for performing additional logic * when shares are added or removed, or when value is distributed. */ -abstract contract AbstractValueDistributor { +abstract contract AValueDistributor { struct UserDistribution { uint256 shares; uint256 cumulativeSum; @@ -34,6 +34,11 @@ abstract contract AbstractValueDistributor { event SharesRemoved(address user, uint256 amount); event ValueDistributed(address user, uint256 amount); + error AmountIsZero(); + error InsufficientOwedValue(address account, uint256 balance, uint256 needed); + error InsufficientSharesAmount(address account, uint256 balance, uint256 needed); + error UserIsZeroAddress(); + /** * @notice Returns the total number of shares. * @return The total number of shares. @@ -88,8 +93,8 @@ abstract contract AbstractValueDistributor { * @param amount_ The amount of shares to add. */ function _addShares(address user_, uint256 amount_) internal virtual { - require(user_ != address(0), "ValueDistributor: zero address is not allowed"); - require(amount_ > 0, "ValueDistributor: amount has to be more than 0"); + if (user_ == address(0)) revert UserIsZeroAddress(); + if (amount_ == 0) revert AmountIsZero(); _update(user_); @@ -109,8 +114,9 @@ abstract contract AbstractValueDistributor { function _removeShares(address user_, uint256 amount_) internal virtual { UserDistribution storage _userDist = _userDistributions[user_]; - require(amount_ > 0, "ValueDistributor: amount has to be more than 0"); - require(amount_ <= _userDist.shares, "ValueDistributor: insufficient amount"); + if (amount_ == 0) revert AmountIsZero(); + if (amount_ > _userDist.shares) + revert InsufficientSharesAmount(user_, _userDist.shares, amount_); _update(user_); @@ -132,8 +138,9 @@ abstract contract AbstractValueDistributor { UserDistribution storage _userDist = _userDistributions[user_]; - require(amount_ > 0, "ValueDistributor: amount has to be more than 0"); - require(amount_ <= _userDist.owedValue, "ValueDistributor: insufficient amount"); + if (amount_ == 0) revert AmountIsZero(); + if (amount_ > _userDist.owedValue) + revert InsufficientOwedValue(user_, _userDist.owedValue, amount_); _userDist.owedValue -= amount_; @@ -154,7 +161,7 @@ abstract contract AbstractValueDistributor { uint256 amount_ = _userDist.owedValue; - require(amount_ > 0, "ValueDistributor: amount has to be more than 0"); + if (amount_ == 0) revert AmountIsZero(); delete _userDist.owedValue; diff --git a/contracts/finance/vesting/Vesting.sol b/contracts/finance/vesting/AVesting.sol similarity index 89% rename from contracts/finance/vesting/Vesting.sol rename to contracts/finance/vesting/AVesting.sol index f6c8ad58..77700c07 100644 --- a/contracts/finance/vesting/Vesting.sol +++ b/contracts/finance/vesting/AVesting.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import {MathUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol"; import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {PRECISION} from "../../utils/Globals.sol"; @@ -68,8 +68,8 @@ import {PRECISION} from "../../utils/Globals.sol"; * * It's not possible to create a schedule with an exponent equal to 0. */ -abstract contract Vesting is Initializable { - using MathUpgradeable for uint256; +abstract contract AVesting is Initializable { + using Math for uint256; using SafeERC20 for IERC20; using EnumerableSet for EnumerableSet.UintSet; @@ -135,10 +135,21 @@ abstract contract Vesting is Initializable { */ event WithdrawnFromVesting(uint256 indexed vestingId, uint256 amount); + error BeneficiaryIsZeroAddress(); + error ExponentIsZero(); + error NothingToWithdraw(); + error StartTimeIsZero(); + error ScheduleInvalidPeriodParameter(uint256 durationInPeriods, uint256 secondsInPeriod); + error ScheduleCliffGreaterThanDuration(uint256 cliffInPeriods, uint256 durationInPeriods); + error UnauthorizedAccount(address account); + error VestingAmountIsZero(); + error VestingTokenIsZeroAddress(); + error VestingPastDate(); + /** * @notice Constructor. */ - function __Vesting_init() internal onlyInitializing {} + function __AVesting_init() internal onlyInitializing {} /** * @notice Withdraws funds from a vesting contract. @@ -147,14 +158,8 @@ abstract contract Vesting is Initializable { function withdrawFromVesting(uint256 vestingId_) public virtual { VestingData storage _vesting = _vestings[vestingId_]; - require( - msg.sender == _vesting.beneficiary, - "VestingWallet: only beneficiary can withdraw from his vesting" - ); - require( - _vesting.paidAmount < _vesting.vestingAmount, - "VestingWallet: nothing to withdraw" - ); + if (msg.sender != _vesting.beneficiary) revert UnauthorizedAccount(msg.sender); + if (_vesting.paidAmount >= _vesting.vestingAmount) revert NothingToWithdraw(); uint256 amountToPay_ = getWithdrawableAmount(vestingId_); address token_ = _vesting.vestingToken; @@ -258,10 +263,7 @@ abstract contract Vesting is Initializable { * @return The ID of the created schedule. */ function _createSchedule(Schedule memory schedule_) internal virtual returns (uint256) { - require( - schedule_.exponent > 0, - "VestingWallet: cannot create schedule with zero exponent" - ); + if (schedule_.exponent == 0) revert ExponentIsZero(); _validateSchedule(schedule_.scheduleData); @@ -282,13 +284,12 @@ abstract contract Vesting is Initializable { Schedule storage _schedule = _schedules[vesting_.scheduleId]; - require( + if ( vesting_.vestingStartTime + _schedule.scheduleData.durationInPeriods * - _schedule.scheduleData.secondsInPeriod > - block.timestamp, - "VestingWallet: cannot create vesting for a past date" - ); + _schedule.scheduleData.secondsInPeriod <= + block.timestamp + ) revert VestingPastDate(); uint256 _currentVestingId = ++vestingId; @@ -409,14 +410,16 @@ abstract contract Vesting is Initializable { * @param schedule_ Base schedule data to be validated. */ function _validateSchedule(BaseSchedule memory schedule_) internal pure { - require( - schedule_.durationInPeriods > 0 && schedule_.secondsInPeriod > 0, - "VestingWallet: cannot create schedule with zero duration or zero seconds in period" - ); - require( - schedule_.cliffInPeriods < schedule_.durationInPeriods, - "VestingWallet: cliff cannot be greater than duration" - ); + if (schedule_.durationInPeriods == 0 || schedule_.secondsInPeriod == 0) + revert ScheduleInvalidPeriodParameter( + schedule_.durationInPeriods, + schedule_.secondsInPeriod + ); + if (schedule_.cliffInPeriods >= schedule_.durationInPeriods) + revert ScheduleCliffGreaterThanDuration( + schedule_.cliffInPeriods, + schedule_.durationInPeriods + ); } /** @@ -424,22 +427,10 @@ abstract contract Vesting is Initializable { * @param vesting_ Vesting data to be validated. */ function _validateVesting(VestingData memory vesting_) internal pure { - require( - vesting_.vestingStartTime > 0, - "VestingWallet: cannot create vesting for zero time" - ); - require( - vesting_.vestingAmount > 0, - "VestingWallet: cannot create vesting for zero amount" - ); - require( - vesting_.beneficiary != address(0), - "VestingWallet: cannot create vesting for zero address" - ); - require( - vesting_.vestingToken != address(0), - "VestingWallet: vesting token cannot be zero address" - ); + if (vesting_.vestingStartTime == 0) revert StartTimeIsZero(); + if (vesting_.vestingAmount == 0) revert VestingAmountIsZero(); + if (vesting_.beneficiary == address(0)) revert BeneficiaryIsZeroAddress(); + if (vesting_.vestingToken == address(0)) revert VestingTokenIsZeroAddress(); } /** diff --git a/contracts/interfaces/access/IMultiOwnable.sol b/contracts/interfaces/access/IMultiOwnable.sol index c59bb5b0..b217074f 100644 --- a/contracts/interfaces/access/IMultiOwnable.sol +++ b/contracts/interfaces/access/IMultiOwnable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @notice The MultiOwnable module diff --git a/contracts/interfaces/access/IRBAC.sol b/contracts/interfaces/access/IRBAC.sol index d9b823f6..a36f79af 100644 --- a/contracts/interfaces/access/IRBAC.sol +++ b/contracts/interfaces/access/IRBAC.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @notice The RBAC module diff --git a/contracts/interfaces/access/extensions/IRBACGroupable.sol b/contracts/interfaces/access/extensions/IRBACGroupable.sol index 5a7cced7..41e97d62 100644 --- a/contracts/interfaces/access/extensions/IRBACGroupable.sol +++ b/contracts/interfaces/access/extensions/IRBACGroupable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @notice The RBAC module diff --git a/contracts/interfaces/compound-rate-keeper/ICompoundRateKeeper.sol b/contracts/interfaces/compound-rate-keeper/ICompoundRateKeeper.sol index 0a4926ad..83b278bf 100644 --- a/contracts/interfaces/compound-rate-keeper/ICompoundRateKeeper.sol +++ b/contracts/interfaces/compound-rate-keeper/ICompoundRateKeeper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @notice The Compound Rate Keeper module diff --git a/contracts/interfaces/proxy/IAdminableProxy.sol b/contracts/interfaces/proxy/IAdminableProxy.sol new file mode 100644 index 00000000..4edfa70d --- /dev/null +++ b/contracts/interfaces/proxy/IAdminableProxy.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import {IERC1967} from "@openzeppelin/contracts/interfaces/IERC1967.sol"; + +/** + * @notice The proxies module + * + * Interface for AdminableProxy. + **/ +interface IAdminableProxy is IERC1967 { + /** + * @notice The function to upgrade the implementation contract with additional setup call if data is nonempty. + */ + function upgradeToAndCall(address, bytes calldata) external payable; + + /** + * @notice The function to return the current implementation address. + */ + function implementation() external view returns (address); +} diff --git a/contracts/interfaces/tokens/ISBT.sol b/contracts/interfaces/tokens/ISBT.sol index 897a0215..37cdba7f 100644 --- a/contracts/interfaces/tokens/ISBT.sol +++ b/contracts/interfaces/tokens/ISBT.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @notice The SBT module diff --git a/contracts/libs/arrays/ArrayHelper.sol b/contracts/libs/arrays/ArrayHelper.sol index 44127484..5167a333 100644 --- a/contracts/libs/arrays/ArrayHelper.sol +++ b/contracts/libs/arrays/ArrayHelper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; @@ -7,6 +7,8 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; * @notice A simple library to work with arrays */ library ArrayHelper { + error InvalidRange(uint256 beginIndex, uint256 endIndex); + /** * @notice The function that searches for the index of the first occurring element, which is * greater than or equal to the `element_`. The time complexity is O(log n) @@ -97,7 +99,7 @@ library ArrayHelper { uint256 beginIndex_, uint256 endIndex_ ) internal view returns (uint256) { - require(beginIndex_ <= endIndex_, "ArrayHelper: wrong range"); + if (beginIndex_ > endIndex_) revert InvalidRange(beginIndex_, endIndex_); if (beginIndex_ == 0) { return prefixes[endIndex_]; diff --git a/contracts/libs/arrays/Paginator.sol b/contracts/libs/arrays/Paginator.sol index d7fb65f7..cb5da5fb 100644 --- a/contracts/libs/arrays/Paginator.sol +++ b/contracts/libs/arrays/Paginator.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; diff --git a/contracts/libs/arrays/SetHelper.sol b/contracts/libs/arrays/SetHelper.sol index 6b7fcca0..3d46e597 100644 --- a/contracts/libs/arrays/SetHelper.sol +++ b/contracts/libs/arrays/SetHelper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; @@ -14,6 +14,18 @@ library SetHelper { using EnumerableSet for EnumerableSet.Bytes32Set; using DynamicSet for *; + error ElementAlreadyExistsAddress(address element); + error ElementAlreadyExistsUint256(uint256 element); + error ElementAlreadyExistsBytes32(bytes32 element); + error ElementAlreadyExistsBytes(bytes element); + error ElementAlreadyExistsString(string element); + + error NoSuchAddress(address element); + error NoSuchUint256(uint256 element); + error NoSuchBytes32(bytes32 element); + error NoSuchBytes(bytes element); + error NoSuchString(string element); + /** * @notice The function to insert an array of elements into the address set * @param set the set to insert the elements into @@ -68,7 +80,7 @@ library SetHelper { */ function strictAdd(EnumerableSet.AddressSet storage set, address[] memory array_) internal { for (uint256 i = 0; i < array_.length; i++) { - require(set.add(array_[i]), "SetHelper: element already exists"); + if (!set.add(array_[i])) revert ElementAlreadyExistsAddress(array_[i]); } } @@ -77,7 +89,7 @@ library SetHelper { */ function strictAdd(EnumerableSet.UintSet storage set, uint256[] memory array_) internal { for (uint256 i = 0; i < array_.length; i++) { - require(set.add(array_[i]), "SetHelper: element already exists"); + if (!set.add(array_[i])) revert ElementAlreadyExistsUint256(array_[i]); } } @@ -86,7 +98,7 @@ library SetHelper { */ function strictAdd(EnumerableSet.Bytes32Set storage set, bytes32[] memory array_) internal { for (uint256 i = 0; i < array_.length; i++) { - require(set.add(array_[i]), "SetHelper: element already exists"); + if (!set.add(array_[i])) revert ElementAlreadyExistsBytes32(array_[i]); } } @@ -95,7 +107,7 @@ library SetHelper { */ function strictAdd(DynamicSet.BytesSet storage set, bytes[] memory array_) internal { for (uint256 i = 0; i < array_.length; i++) { - require(set.add(array_[i]), "SetHelper: element already exists"); + if (!set.add(array_[i])) revert ElementAlreadyExistsBytes(array_[i]); } } @@ -104,7 +116,7 @@ library SetHelper { */ function strictAdd(DynamicSet.StringSet storage set, string[] memory array_) internal { for (uint256 i = 0; i < array_.length; i++) { - require(set.add(array_[i]), "SetHelper: element already exists"); + if (!set.add(array_[i])) revert ElementAlreadyExistsString(array_[i]); } } @@ -162,7 +174,7 @@ library SetHelper { */ function strictRemove(EnumerableSet.AddressSet storage set, address[] memory array_) internal { for (uint256 i = 0; i < array_.length; i++) { - require(set.remove(array_[i]), "SetHelper: no such element"); + if (!set.remove(array_[i])) revert NoSuchAddress(array_[i]); } } @@ -171,7 +183,7 @@ library SetHelper { */ function strictRemove(EnumerableSet.UintSet storage set, uint256[] memory array_) internal { for (uint256 i = 0; i < array_.length; i++) { - require(set.remove(array_[i]), "SetHelper: no such element"); + if (!set.remove(array_[i])) revert NoSuchUint256(array_[i]); } } @@ -180,7 +192,7 @@ library SetHelper { */ function strictRemove(EnumerableSet.Bytes32Set storage set, bytes32[] memory array_) internal { for (uint256 i = 0; i < array_.length; i++) { - require(set.remove(array_[i]), "SetHelper: no such element"); + if (!set.remove(array_[i])) revert NoSuchBytes32(array_[i]); } } @@ -189,7 +201,7 @@ library SetHelper { */ function strictRemove(DynamicSet.BytesSet storage set, bytes[] memory array_) internal { for (uint256 i = 0; i < array_.length; i++) { - require(set.remove(array_[i]), "SetHelper: no such element"); + if (!set.remove(array_[i])) revert NoSuchBytes(array_[i]); } } @@ -198,7 +210,7 @@ library SetHelper { */ function strictRemove(DynamicSet.StringSet storage set, string[] memory array_) internal { for (uint256 i = 0; i < array_.length; i++) { - require(set.remove(array_[i]), "SetHelper: no such element"); + if (!set.remove(array_[i])) revert NoSuchString(array_[i]); } } } diff --git a/contracts/libs/bn/U512.sol b/contracts/libs/bn/U512.sol index 7c3bb25c..805b2918 100644 --- a/contracts/libs/bn/U512.sol +++ b/contracts/libs/bn/U512.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; type uint512 is uint256; type call512 is uint256; @@ -1540,7 +1540,7 @@ library U512 { */ function _modsub(call512 call_, uint512 a_, uint512 b_, uint512 m_, uint512 r_) private view { unchecked { - int cmp_ = cmp(a_, b_); + int256 cmp_ = cmp(a_, b_); if (cmp_ >= 0) { _sub(a_, b_, r_); diff --git a/contracts/libs/crypto/ECDSA256.sol b/contracts/libs/crypto/ECDSA256.sol index d5c8e8f1..e3b729c3 100644 --- a/contracts/libs/crypto/ECDSA256.sol +++ b/contracts/libs/crypto/ECDSA256.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT // Reference: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/cryptography/P256.sol -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; + +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; /** * @notice Cryptography module @@ -23,12 +25,14 @@ library ECDSA256 { uint256 lowSmax; } + // solhint-disable-next-line contract-name-capwords struct _JPoint { uint256 x; uint256 y; uint256 z; } + // solhint-disable-next-line contract-name-capwords struct _Inputs { uint256 r; uint256 s; @@ -36,6 +40,8 @@ library ECDSA256 { uint256 y; } + error LengthIsNot64(); + /** * @notice The function to verify the ECDSA signature * @param curveParams_ the 256-bit curve parameters. `lowSmax` is `n / 2`. @@ -69,7 +75,7 @@ library ECDSA256 { uint256 u2_; { - uint256 w_ = _invModPrime(inputs_.s, curveParams_.n); + uint256 w_ = Math.invModPrime(inputs_.s, curveParams_.n); u1_ = mulmod(uint256(hashedMessage_), w_, curveParams_.n); u2_ = mulmod(inputs_.r, w_, curveParams_.n); } @@ -137,7 +143,7 @@ library ECDSA256 { ) private view returns (uint256 ax_, uint256 ay_) { if (point_.z == 0) return (0, 0); - uint256 zInverse_ = _invModPrime(point_.z, p_); + uint256 zInverse_ = Math.invModPrime(point_.z, p_); assembly ("memory-safe") { let zzInverse_ := mulmod(zInverse_, zInverse_, p_) @@ -373,7 +379,7 @@ library ECDSA256 { bytes memory from2_ ) private pure returns (uint256 leftPart_, uint256 rightPart_) { unchecked { - require(from2_.length == 64, "ECDSA256: length is not 64"); + if (from2_.length != 64) revert LengthIsNot64(); assembly ("memory-safe") { leftPart_ := mload(add(from2_, 32)) @@ -383,41 +389,4 @@ library ECDSA256 { return (leftPart_, rightPart_); } } - - /** - * @dev Calculate the modular multiplicative inverse via Fermat's little theorem. - * Only works if `modulus_` is known to be a prime greater than `2`. - */ - function _invModPrime(uint256 base_, uint256 modulus_) private view returns (uint256) { - unchecked { - return _modExp(base_, modulus_ - 2, modulus_); - } - } - - /** - * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m). - */ - function _modExp( - uint256 base_, - uint256 exponent_, - uint256 modulus_ - ) private view returns (uint256 result_) { - require(modulus_ != 0, "ECDSA256: division by zero"); - - assembly ("memory-safe") { - let pointer_ := mload(0x40) - - mstore(pointer_, 0x20) - mstore(add(pointer_, 0x20), 0x20) - mstore(add(pointer_, 0x40), 0x20) - mstore(add(pointer_, 0x60), base_) - mstore(add(pointer_, 0x80), exponent_) - mstore(add(pointer_, 0xa0), modulus_) - - pop(staticcall(gas(), 0x05, pointer_, 0xc0, 0x00, 0x20)) - result_ := mload(0x00) - } - - return result_; - } } diff --git a/contracts/libs/crypto/ECDSA384.sol b/contracts/libs/crypto/ECDSA384.sol index 52230994..4e492705 100644 --- a/contracts/libs/crypto/ECDSA384.sol +++ b/contracts/libs/crypto/ECDSA384.sol @@ -1,8 +1,10 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; +// Separate imports due to IntelliJ Solidity plugin issues import {call512, uint512} from "../bn/U512.sol"; import {U512} from "../bn/U512.sol"; + import {MemoryUtils} from "../utils/MemoryUtils.sol"; /** @@ -29,6 +31,7 @@ library ECDSA384 { bytes lowSmax; } + // solhint-disable-next-line contract-name-capwords struct _Parameters { uint512 a; uint512 b; @@ -39,6 +42,7 @@ library ECDSA384 { uint512 lowSmax; } + // solhint-disable-next-line contract-name-capwords struct _Inputs { uint512 r; uint512 s; diff --git a/contracts/libs/crypto/ECDSA512.sol b/contracts/libs/crypto/ECDSA512.sol index 3260142e..3f62c359 100644 --- a/contracts/libs/crypto/ECDSA512.sol +++ b/contracts/libs/crypto/ECDSA512.sol @@ -1,8 +1,10 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; +// Separate imports due to IntelliJ Solidity plugin issues import {call512, uint512} from "../bn/U512.sol"; import {U512} from "../bn/U512.sol"; + import {MemoryUtils} from "../utils/MemoryUtils.sol"; /** @@ -29,6 +31,7 @@ library ECDSA512 { bytes lowSmax; } + // solhint-disable-next-line contract-name-capwords struct _Parameters { uint512 a; uint512 b; @@ -39,6 +42,7 @@ library ECDSA512 { uint512 lowSmax; } + // solhint-disable-next-line contract-name-capwords struct _Inputs { uint512 r; uint512 s; diff --git a/contracts/libs/crypto/RSASSAPSS.sol b/contracts/libs/crypto/RSASSAPSS.sol index f26546db..99e984a5 100644 --- a/contracts/libs/crypto/RSASSAPSS.sol +++ b/contracts/libs/crypto/RSASSAPSS.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @notice Cryptography module diff --git a/contracts/libs/data-structures/AvlTree.sol b/contracts/libs/data-structures/AvlTree.sol index 1c0c04d8..20128907 100644 --- a/contracts/libs/data-structures/AvlTree.sol +++ b/contracts/libs/data-structures/AvlTree.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; +// solhint-disable-previous-line one-contract-per-file import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; @@ -66,6 +67,11 @@ library AvlTree { Tree _tree; } + error NodeAlreadyExists(bytes32 key); + error NodeDoesNotExist(bytes32 key); + error KeyIsZero(); + error TreeNotEmpty(); + /** * @notice The function to set a custom comparator function, that will be used to build the uint256 tree. * @param tree self. @@ -424,7 +430,7 @@ library AvlTree { Tree storage tree, function(bytes32, bytes32) view returns (int256) comparator_ ) private { - require(_size(tree) == 0, "AvlTree: the tree must be empty"); + if (_size(tree) != 0) revert TreeNotEmpty(); tree.isCustomComparatorSet = true; @@ -432,7 +438,7 @@ library AvlTree { } function _insert(Tree storage tree, bytes32 key_, bytes32 value_) private { - require(key_ != 0, "AvlTree: key is not allowed to be 0"); + if (key_ == 0) revert KeyIsZero(); tree.totalCount++; @@ -448,7 +454,7 @@ library AvlTree { } function _remove(Tree storage tree, bytes32 key_) private { - require(key_ != 0, "AvlTree: key is not allowed to be 0"); + if (key_ == 0) revert KeyIsZero(); tree.root = _removeNode(tree.tree, tree.root, 0, bytes32(key_), _getComparator(tree)); @@ -490,7 +496,7 @@ library AvlTree { comparator_ ); } else if (comparison_ == 0) { - revert("AvlTree: the node already exists"); + revert NodeAlreadyExists(key_); } else { _tree[node_].right = _insertNode( _tree, @@ -513,7 +519,7 @@ library AvlTree { bytes32 key_, function(bytes32, bytes32) view returns (int256) comparator_ ) private returns (uint64) { - require(node_ != 0, "AvlTree: the node doesn't exist"); + if (node_ == 0) revert NodeDoesNotExist(key_); int256 comparison_ = comparator_(key_, _tree[node_].key); @@ -683,7 +689,7 @@ library AvlTree { function _get(Tree storage tree, bytes32 key_) private view returns (bytes32) { uint64 index_ = _search(tree.tree, tree.root, key_, _getComparator(tree)); - require(index_ != 0, "AvlTree: the node doesn't exist"); + if (index_ == 0) revert NodeDoesNotExist(key_); return tree.tree[index_].value; } @@ -765,6 +771,8 @@ library Traversal { uint64 currentNode; } + error NoNodesLeft(); + /** * @notice The function to check if the iterator is currently valid (has not reached the end of the traversal). * @param iterator_ self. @@ -853,7 +861,7 @@ library Traversal { ) internal view returns (bytes32, bytes32) { uint64 currentNodeIndex_ = iterator_.currentNode; - require(currentNodeIndex_ != 0, "Traversal: no more nodes"); + if (currentNodeIndex_ == 0) revert NoNodesLeft(); AvlTree.Node memory node_ = _getNode(iterator_.treeMappingSlot, currentNodeIndex_); diff --git a/contracts/libs/data-structures/CartesianMerkleTree.sol b/contracts/libs/data-structures/CartesianMerkleTree.sol index 0be9d50b..0736eb64 100644 --- a/contracts/libs/data-structures/CartesianMerkleTree.sol +++ b/contracts/libs/data-structures/CartesianMerkleTree.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @notice Cartesian Merkle Tree Module @@ -48,6 +48,17 @@ library CartesianMerkleTree { CMT _treaple; } + error TreapleNotInitialized(); + error TreapleAlreadyInitialized(); + error TreapleNotEmpty(); + + error ZeroDesiredProofSize(); + error ProofSizeTooSmall(uint256 attemptedIndex, uint256 maxIndex); + + error ZeroKeyProvided(); + error KeyAlreadyExists(); + error NodeDoesNotExist(); + /** * @notice The function to initialize the Cartesian Merkle tree. * Under the hood it sets the desired proof size of the CMT proofs, therefore can be considered @@ -608,21 +619,18 @@ library CartesianMerkleTree { bytes32 internal constant ZERO_HASH = bytes32(0); modifier onlyInitialized(CMT storage treaple) { - require(_isInitialized(treaple), "CartesianMerkleTree: treaple is not initialized"); + if (!_isInitialized(treaple)) revert TreapleNotInitialized(); _; } function _initialize(CMT storage treaple, uint32 desiredProofSize_) private { - require(!_isInitialized(treaple), "CartesianMerkleTree: treaple is already initialized"); + if (_isInitialized(treaple)) revert TreapleAlreadyInitialized(); _setDesiredProofSize(treaple, desiredProofSize_); } function _setDesiredProofSize(CMT storage treaple, uint32 desiredProofSize_) private { - require( - desiredProofSize_ > 0, - "CartesianMerkleTree: desired proof size must be greater than zero" - ); + if (desiredProofSize_ == 0) revert ZeroDesiredProofSize(); treaple.desiredProofSize = desiredProofSize_; } @@ -631,7 +639,7 @@ library CartesianMerkleTree { CMT storage treaple, function(bytes32, bytes32, bytes32) view returns (bytes32) hash3_ ) private { - require(_nodesCount(treaple) == 0, "CartesianMerkleTree: treaple is not empty"); + if (_nodesCount(treaple) != 0) revert TreapleNotEmpty(); treaple.isCustomHasherSet = true; @@ -639,13 +647,13 @@ library CartesianMerkleTree { } function _add(CMT storage treaple, bytes32 key_) private onlyInitialized(treaple) { - require(key_ != 0, "CartesianMerkleTree: the key can't be zero"); + if (key_ == 0) revert ZeroKeyProvided(); treaple.merkleRootId = uint64(_add(treaple, treaple.merkleRootId, key_)); } function _remove(CMT storage treaple, bytes32 key_) private onlyInitialized(treaple) { - require(key_ != 0, "CartesianMerkleTree: the key can't be zero"); + if (key_ == 0) revert ZeroKeyProvided(); treaple.merkleRootId = uint64(_remove(treaple, treaple.merkleRootId, key_)); } @@ -661,7 +669,7 @@ library CartesianMerkleTree { return _newNode(treaple, key_); } - require(rootNode.key != key_, "CartesianMerkleTree: the key already exists"); + if (rootNode.key == key_) revert KeyAlreadyExists(); if (rootNode.key > key_) { rootNode.childLeft = uint64(_add(treaple, rootNode.childLeft, key_)); @@ -691,7 +699,7 @@ library CartesianMerkleTree { ) private returns (uint256) { Node storage rootNode = treaple.nodes[uint64(rootNodeId_)]; - require(rootNode.key != 0, "CartesianMerkleTree: the node does not exist"); + if (rootNode.key == 0) revert NodeDoesNotExist(); if (key_ < rootNode.key) { rootNode.childLeft = uint64(_remove(treaple, rootNode.childLeft, key_)); @@ -882,10 +890,9 @@ library CartesianMerkleTree { uint256 currentSiblingsIndex_, bytes32 siblingToAdd_ ) private pure { - require( - currentSiblingsIndex_ < proof_.siblings.length, - "CartesianMerkleTree: desired proof size is too low" - ); + if (currentSiblingsIndex_ >= proof_.siblings.length) { + revert ProofSizeTooSmall(currentSiblingsIndex_, proof_.siblings.length); + } proof_.siblings[currentSiblingsIndex_] = siblingToAdd_; } diff --git a/contracts/libs/data-structures/DynamicSet.sol b/contracts/libs/data-structures/DynamicSet.sol index ab3c1290..f1082dd6 100644 --- a/contracts/libs/data-structures/DynamicSet.sol +++ b/contracts/libs/data-structures/DynamicSet.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @notice Dynamic Set Library diff --git a/contracts/libs/data-structures/IncrementalMerkleTree.sol b/contracts/libs/data-structures/IncrementalMerkleTree.sol index 808869eb..8b3f4d37 100644 --- a/contracts/libs/data-structures/IncrementalMerkleTree.sol +++ b/contracts/libs/data-structures/IncrementalMerkleTree.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @notice Incremental Merkle Tree module @@ -57,6 +57,10 @@ library IncrementalMerkleTree { IMT _tree; } + error NewHeightMustBeGreater(uint256 currentHeight, uint256 newHeight); + error TreeIsNotEmpty(); + error TreeIsFull(); + /** * @notice The function to set the height of the uint256 tree. * Complexity is O(1). @@ -301,10 +305,9 @@ library IncrementalMerkleTree { } function _setHeight(IMT storage tree, uint256 height_) private { - require( - height_ > _height(tree), - "IncrementalMerkleTree: the height must be greater than the current one" - ); + uint256 currentHeight_ = _height(tree); + + if (height_ <= currentHeight_) revert NewHeightMustBeGreater(currentHeight_, height_); tree.isStrictHeightSet = true; @@ -318,7 +321,7 @@ library IncrementalMerkleTree { function(bytes32) view returns (bytes32) hash1_, function(bytes32, bytes32) view returns (bytes32) hash2_ ) private { - require(_length(tree) == 0, "IncrementalMerkleTree: the tree must be empty"); + if (_length(tree) != 0) revert TreeIsNotEmpty(); tree.isCustomHasherSet = true; @@ -353,9 +356,7 @@ library IncrementalMerkleTree { } if (index_ == treeHeight_) { - if (tree.isStrictHeightSet) { - revert("IncrementalMerkleTree: the tree is full"); - } + if (tree.isStrictHeightSet) revert TreeIsFull(); tree.branches.push(resultValue_); } else { diff --git a/contracts/libs/data-structures/PriorityQueue.sol b/contracts/libs/data-structures/PriorityQueue.sol index 2eeb8636..e74740a3 100644 --- a/contracts/libs/data-structures/PriorityQueue.sol +++ b/contracts/libs/data-structures/PriorityQueue.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {TypeCaster} from "../utils/TypeCaster.sol"; @@ -40,6 +40,8 @@ library PriorityQueue { Queue _queue; } + error QueueIsEmpty(); + /** * @notice The function to add an element to the uint256 queue. O(log(n)) complex * @param queue self @@ -368,6 +370,6 @@ library PriorityQueue { } function _requireNotEmpty(Queue storage queue) private view { - require(_length(queue) > 0, "PriorityQueue: empty queue"); + if (_length(queue) == 0) revert QueueIsEmpty(); } } diff --git a/contracts/libs/data-structures/SparseMerkleTree.sol b/contracts/libs/data-structures/SparseMerkleTree.sol index 7a8164f3..2c24773a 100644 --- a/contracts/libs/data-structures/SparseMerkleTree.sol +++ b/contracts/libs/data-structures/SparseMerkleTree.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @notice Sparse Merkle Tree Module @@ -71,6 +71,17 @@ library SparseMerkleTree { SMT _tree; } + error KeyAlreadyExists(bytes32 key); + error LeafDoesNotMatch(bytes32 currentKey, bytes32 key); + error MaxDepthExceedsHardCap(uint32 maxDepth); + error MaxDepthIsZero(); + error MaxDepthReached(); + error NewMaxDepthMustBeLarger(uint32 currentDepth, uint32 newDepth); + error NodeDoesNotExist(uint256 nodeId); + error TreeAlreadyInitialized(); + error TreeNotInitialized(); + error TreeIsNotEmpty(); + /** * @notice The function to initialize the Merkle tree. * Under the hood it sets the maximum depth of the Merkle tree, therefore can be considered @@ -679,23 +690,23 @@ library SparseMerkleTree { } modifier onlyInitialized(SMT storage tree) { - require(_isInitialized(tree), "SparseMerkleTree: tree is not initialized"); + if (!_isInitialized(tree)) revert TreeNotInitialized(); _; } function _initialize(SMT storage tree, uint32 maxDepth_) private { - require(!_isInitialized(tree), "SparseMerkleTree: tree is already initialized"); + if (_isInitialized(tree)) revert TreeAlreadyInitialized(); _setMaxDepth(tree, maxDepth_); } function _setMaxDepth(SMT storage tree, uint32 maxDepth_) private { - require(maxDepth_ > 0, "SparseMerkleTree: max depth must be greater than zero"); - require(maxDepth_ > tree.maxDepth, "SparseMerkleTree: max depth can only be increased"); - require( - maxDepth_ <= MAX_DEPTH_HARD_CAP, - "SparseMerkleTree: max depth is greater than hard cap" - ); + if (maxDepth_ == 0) revert MaxDepthIsZero(); + + uint32 currentDepth_ = tree.maxDepth; + + if (maxDepth_ <= currentDepth_) revert NewMaxDepthMustBeLarger(currentDepth_, maxDepth_); + if (maxDepth_ > MAX_DEPTH_HARD_CAP) revert MaxDepthExceedsHardCap(maxDepth_); tree.maxDepth = maxDepth_; } @@ -705,7 +716,7 @@ library SparseMerkleTree { function(bytes32, bytes32) view returns (bytes32) hash2_, function(bytes32, bytes32, bytes32) view returns (bytes32) hash3_ ) private { - require(_nodesCount(tree) == 0, "SparseMerkleTree: tree is not empty"); + if (_nodesCount(tree) != 0) revert TreeIsNotEmpty(); tree.isCustomHasherSet = true; @@ -764,9 +775,7 @@ library SparseMerkleTree { if (currentNode_.nodeType == NodeType.EMPTY) { return _setNode(tree, newLeaf_); } else if (currentNode_.nodeType == NodeType.LEAF) { - if (currentNode_.key == newLeaf_.key) { - revert("SparseMerkleTree: the key already exists"); - } + if (currentNode_.key == newLeaf_.key) revert KeyAlreadyExists(newLeaf_.key); return _pushLeaf(tree, newLeaf_, currentNode_, nodeId_, currentDepth_); } else { @@ -797,11 +806,9 @@ library SparseMerkleTree { Node memory currentNode_ = tree.nodes[nodeId_]; if (currentNode_.nodeType == NodeType.EMPTY) { - revert("SparseMerkleTree: the node does not exist"); + revert NodeDoesNotExist(nodeId_); } else if (currentNode_.nodeType == NodeType.LEAF) { - if (currentNode_.key != key_) { - revert("SparseMerkleTree: the leaf does not match"); - } + if (currentNode_.key != key_) revert LeafDoesNotMatch(currentNode_.key, key_); _deleteNode(tree, nodeId_); @@ -865,11 +872,10 @@ library SparseMerkleTree { Node memory currentNode_ = tree.nodes[nodeId_]; if (currentNode_.nodeType == NodeType.EMPTY) { - revert("SparseMerkleTree: the node does not exist"); + revert NodeDoesNotExist(nodeId_); } else if (currentNode_.nodeType == NodeType.LEAF) { - if (currentNode_.key != newLeaf_.key) { - revert("SparseMerkleTree: the leaf does not match"); - } + if (currentNode_.key != newLeaf_.key) + revert LeafDoesNotMatch(currentNode_.key, newLeaf_.key); tree.nodes[nodeId_] = newLeaf_; currentNode_ = newLeaf_; @@ -891,7 +897,7 @@ library SparseMerkleTree { uint256 oldLeafId_, uint16 currentDepth_ ) private returns (uint256) { - require(currentDepth_ < tree.maxDepth, "SparseMerkleTree: max depth reached"); + if (currentDepth_ >= tree.maxDepth) revert MaxDepthReached(); Node memory newNodeMiddle_; bool newLeafBitAtDepth_ = (uint256(newLeaf_.key) >> currentDepth_) & 1 == 1; diff --git a/contracts/libs/data-structures/memory/Vector.sol b/contracts/libs/data-structures/memory/Vector.sol index f8c85301..6b08658a 100644 --- a/contracts/libs/data-structures/memory/Vector.sol +++ b/contracts/libs/data-structures/memory/Vector.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {TypeCaster} from "../../utils/TypeCaster.sol"; @@ -42,6 +42,9 @@ library Vector { Vector _vector; } + error IndexOutOfBounds(uint256 index, uint256 vectorLength); + error PopEmptyVector(); + /** * @notice The UintVector constructor, creates an empty vector instance, O(1) complex * @return vector the newly created instance @@ -367,7 +370,7 @@ library Vector { function _pop(Vector memory vector) private pure { uint256 length_ = _length(vector); - require(length_ > 0, "Vector: empty vector"); + if (length_ == 0) revert PopEmptyVector(); assembly { mstore(mload(add(vector, 0x20)), sub(length_, 0x1)) @@ -423,7 +426,7 @@ library Vector { } function _requireInBounds(Vector memory vector, uint256 index_) private pure { - require(index_ < _length(vector), "Vector: out of bounds"); + if (index_ >= _length(vector)) revert IndexOutOfBounds(index_, _length(vector)); } function _clean(uint256 dataPointer_, uint256 slots_) private pure { diff --git a/contracts/libs/utils/DecimalsConverter.sol b/contracts/libs/utils/DecimalsConverter.sol index 51b1e8e2..d6e19764 100644 --- a/contracts/libs/utils/DecimalsConverter.sol +++ b/contracts/libs/utils/DecimalsConverter.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; -import {ERC20, IERC20, IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; /** * @notice This library is used to convert numbers that use token's N decimals to M decimals. @@ -36,6 +36,8 @@ import {ERC20, IERC20, IERC20Metadata} from "@openzeppelin/contracts/token/ERC20 * ``` */ library DecimalsConverter { + error ConversionFailed(); + /** * @notice The function to get the decimals of ERC20 token. Needed for bytecode optimization * @param token_ the ERC20 token @@ -234,7 +236,7 @@ library DecimalsConverter { } function _safe(uint256 amount_) private pure returns (uint256) { - require(amount_ > 0, "DecimalsConverter: conversion failed"); + if (amount_ == 0) revert ConversionFailed(); return amount_; } diff --git a/contracts/libs/utils/MemoryUtils.sol b/contracts/libs/utils/MemoryUtils.sol index 6d714fed..e167c725 100644 --- a/contracts/libs/utils/MemoryUtils.sol +++ b/contracts/libs/utils/MemoryUtils.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @title MemoryUtils diff --git a/contracts/libs/utils/ReturnDataProxy.sol b/contracts/libs/utils/ReturnDataProxy.sol index 714219ed..d0494945 100644 --- a/contracts/libs/utils/ReturnDataProxy.sol +++ b/contracts/libs/utils/ReturnDataProxy.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @notice This is a pure assembly library for "yielding" the returned data without doubling the diff --git a/contracts/libs/utils/TypeCaster.sol b/contracts/libs/utils/TypeCaster.sol index aaf6c540..9bfde5fe 100644 --- a/contracts/libs/utils/TypeCaster.sol +++ b/contracts/libs/utils/TypeCaster.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @notice This library simplifies non-obvious type castings. diff --git a/contracts/libs/zkp/snarkjs/VerifierHelper.sol b/contracts/libs/zkp/Groth16VerifierHelper.sol similarity index 91% rename from contracts/libs/zkp/snarkjs/VerifierHelper.sol rename to contracts/libs/zkp/Groth16VerifierHelper.sol index 422b8f03..e766d5f8 100644 --- a/contracts/libs/zkp/snarkjs/VerifierHelper.sol +++ b/contracts/libs/zkp/Groth16VerifierHelper.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; /** * @notice This library is used to simplify the interaction with autogenerated contracts - * that use [snarkjs](https://www.npmjs.com/package/snarkjs) to verify ZK proofs. + * that use [snarkjs](https://www.npmjs.com/package/snarkjs) to verify Groth16 ZK proofs. * * The main problem with these contracts is that the verification function always has the same signature, except for one parameter. * The `input` parameter is a static array `uint256`, the size of which depends on the number of public outputs of ZK proof, @@ -14,7 +14,7 @@ import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; * With this library there is no need to create many different interfaces for each circuit. * Moreover, the library functions accept dynamic arrays of public signals, so you don't need to convert them manually to static ones. */ -library VerifierHelper { +library Groth16VerifierHelper { using Strings for uint256; struct ProofPoints { @@ -23,6 +23,9 @@ library VerifierHelper { uint256[2] c; } + error InvalidPublicSignalsCount(uint256 arrayLength, uint256 pubSignalsCount); + error FailedToCallVerifyProof(); + /** * @notice Function to call the `verifyProof` function on the `verifier` contract. * The ZK proof points are wrapped in a structure for convenience @@ -82,10 +85,8 @@ library VerifierHelper { ProofPoints memory proofPoints_, uint256 pubSignalsCount_ ) internal view returns (bool) { - require( - pubSignals_.length == pubSignalsCount_, - "VerifierHelper: invalid public signals count" - ); + if (pubSignals_.length != pubSignalsCount_) + revert InvalidPublicSignalsCount(pubSignals_.length, pubSignalsCount_); return _verifyProof( @@ -117,10 +118,8 @@ library VerifierHelper { uint256[2] memory c_, uint256 pubSignalsCount_ ) internal view returns (bool) { - require( - pubSignals_.length == pubSignalsCount_, - "VerifierHelper: invalid public signals count" - ); + if (pubSignals_.length != pubSignalsCount_) + revert InvalidPublicSignalsCount(pubSignals_.length, pubSignalsCount_); return _verifyProof(verifier_, a_, b_, c_, pubSignals_, pubSignalsCount_); } @@ -146,7 +145,7 @@ library VerifierHelper { abi.encodePacked(abi.encodeWithSignature(funcSign_, a_, b_, c_), pubSignals_) ); - require(success_, "VerifierHelper: failed to call verifyProof function"); + if (!success_) revert FailedToCallVerifyProof(); return abi.decode(returnData_, (bool)); } diff --git a/contracts/mock/access/MerkleWhitelistedMock.sol b/contracts/mock/access/MerkleWhitelistedMock.sol index 16f3d87d..974d611b 100644 --- a/contracts/mock/access/MerkleWhitelistedMock.sol +++ b/contracts/mock/access/MerkleWhitelistedMock.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; -import {MerkleWhitelisted} from "../../access/MerkleWhitelisted.sol"; +import {AMerkleWhitelisted} from "../../access/AMerkleWhitelisted.sol"; -contract MerkleWhitelistedMock is MerkleWhitelisted { +contract MerkleWhitelistedMock is AMerkleWhitelisted { event WhitelistedUser(); event WhitelistedData(); diff --git a/contracts/mock/access/MultiOwnableMock.sol b/contracts/mock/access/MultiOwnableMock.sol index 08899701..673932c6 100644 --- a/contracts/mock/access/MultiOwnableMock.sol +++ b/contracts/mock/access/MultiOwnableMock.sol @@ -1,14 +1,15 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; -import {MultiOwnable} from "./../../access/MultiOwnable.sol"; +import {AMultiOwnable} from "./../../access/AMultiOwnable.sol"; -contract MultiOwnableMock is MultiOwnable { +contract MultiOwnableMock is AMultiOwnable { function __MultiOwnableMock_init() external initializer { - __MultiOwnable_init(); + __AMultiOwnable_init(); } function mockInit() external { - __MultiOwnable_init(); + __AMultiOwnable_init(); } } diff --git a/contracts/mock/access/PermanentOwnableMock.sol b/contracts/mock/access/PermanentOwnableMock.sol index 36ef1f45..0226dac1 100644 --- a/contracts/mock/access/PermanentOwnableMock.sol +++ b/contracts/mock/access/PermanentOwnableMock.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; -import {PermanentOwnable} from "../../access/PermanentOwnable.sol"; +import {APermanentOwnable} from "../../access/APermanentOwnable.sol"; -contract PermanentOwnableMock is PermanentOwnable { +contract PermanentOwnableMock is APermanentOwnable { event ValidOwner(); - constructor(address _owner) PermanentOwnable(_owner) {} + constructor(address _owner) APermanentOwnable(_owner) {} function onlyOwnerMethod() external onlyOwner { emit ValidOwner(); diff --git a/contracts/mock/access/RBACMock.sol b/contracts/mock/access/RBACMock.sol index 713900e4..348bc875 100644 --- a/contracts/mock/access/RBACMock.sol +++ b/contracts/mock/access/RBACMock.sol @@ -1,20 +1,21 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {TypeCaster} from "../../libs/utils/TypeCaster.sol"; -import {RBAC} from "../../access/RBAC.sol"; +import {ARBAC} from "../../access/ARBAC.sol"; -contract RBACMock is RBAC { +contract RBACMock is ARBAC { using TypeCaster for string; function __RBACMock_init() external initializer { - __RBAC_init(); + __ARBAC_init(); _grantRoles(msg.sender, MASTER_ROLE.asSingletonArray()); } function mockInit() external { - __RBAC_init(); + __ARBAC_init(); } } diff --git a/contracts/mock/access/extensions/RBACGroupableMock.sol b/contracts/mock/access/extensions/RBACGroupableMock.sol index 2e1ab826..f2b2618f 100644 --- a/contracts/mock/access/extensions/RBACGroupableMock.sol +++ b/contracts/mock/access/extensions/RBACGroupableMock.sol @@ -1,20 +1,21 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {TypeCaster} from "../../../libs/utils/TypeCaster.sol"; -import {RBACGroupable} from "../../../access/extensions/RBACGroupable.sol"; +import {ARBACGroupable} from "../../../access/extensions/ARBACGroupable.sol"; -contract RBACGroupableMock is RBACGroupable { +contract RBACGroupableMock is ARBACGroupable { using TypeCaster for string; function __RBACGroupableMock_init() external initializer { - __RBACGroupable_init(); + __ARBACGroupable_init(); _grantRoles(msg.sender, MASTER_ROLE.asSingletonArray()); } function mockInit() external { - __RBACGroupable_init(); + __ARBACGroupable_init(); } } diff --git a/contracts/mock/contracts-registry/ContractsRegistryMock.sol b/contracts/mock/contracts-registry/ContractsRegistryMock.sol index da9c6b35..ba5b0b4e 100644 --- a/contracts/mock/contracts-registry/ContractsRegistryMock.sol +++ b/contracts/mock/contracts-registry/ContractsRegistryMock.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.22; import {OwnableContractsRegistry} from "../../contracts-registry/presets/OwnableContractsRegistry.sol"; @@ -8,7 +9,7 @@ contract ContractsRegistryMock is OwnableContractsRegistry { string public constant TOKEN_NAME = "TOKEN"; function mockInit() external { - __ContractsRegistry_init(); + __AContractsRegistry_init(); } function getDependantContract() external view returns (address) { diff --git a/contracts/mock/contracts-registry/DependantMock.sol b/contracts/mock/contracts-registry/DependantMock.sol index c67e9cc4..c91466b4 100644 --- a/contracts/mock/contracts-registry/DependantMock.sol +++ b/contracts/mock/contracts-registry/DependantMock.sol @@ -1,11 +1,12 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.22; -import {AbstractDependant} from "../../contracts-registry/AbstractDependant.sol"; +import {ADependant} from "../../contracts-registry/ADependant.sol"; import {ContractsRegistryMock} from "./ContractsRegistryMock.sol"; -contract DependantMock is AbstractDependant { +contract DependantMock is ADependant { address public token; function setDependencies(address contractsRegistry_, bytes memory) public override dependant { diff --git a/contracts/mock/contracts-registry/DependantUpgradeMock.sol b/contracts/mock/contracts-registry/DependantUpgradeMock.sol index 5fe3d7b4..054f63e2 100644 --- a/contracts/mock/contracts-registry/DependantUpgradeMock.sol +++ b/contracts/mock/contracts-registry/DependantUpgradeMock.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.22; import {DependantMock} from "./DependantMock.sol"; diff --git a/contracts/mock/contracts-registry/pools/ContractsRegistryPoolMock.sol b/contracts/mock/contracts-registry/pools/ContractsRegistryPoolMock.sol index 80155216..f8c4a590 100644 --- a/contracts/mock/contracts-registry/pools/ContractsRegistryPoolMock.sol +++ b/contracts/mock/contracts-registry/pools/ContractsRegistryPoolMock.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.22; import {OwnableContractsRegistry} from "../../../contracts-registry/presets/OwnableContractsRegistry.sol"; diff --git a/contracts/mock/contracts-registry/pools/PoolContractsRegistryMock.sol b/contracts/mock/contracts-registry/pools/PoolContractsRegistryMock.sol index d9cf5924..c1913e6c 100644 --- a/contracts/mock/contracts-registry/pools/PoolContractsRegistryMock.sol +++ b/contracts/mock/contracts-registry/pools/PoolContractsRegistryMock.sol @@ -1,23 +1,26 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.22; import {ContractsRegistryPoolMock} from "./ContractsRegistryPoolMock.sol"; -import {OwnablePoolContractsRegistry} from "../../../contracts-registry/pools/presets/OwnablePoolContractsRegistry.sol"; +import {AOwnablePoolContractsRegistry} from "../../../contracts-registry/pools/presets/AOwnablePoolContractsRegistry.sol"; -contract PoolContractsRegistryMock is OwnablePoolContractsRegistry { +contract PoolContractsRegistryMock is AOwnablePoolContractsRegistry { string public constant POOL_1_NAME = "POOL_1"; string public constant POOL_2_NAME = "POOL_2"; address internal _poolFactory; + error CallerNotAFactory(address caller, address factory); + modifier onlyPoolFactory() { - require(_poolFactory == msg.sender, "PoolContractsRegistry: not a factory"); + if (_poolFactory != msg.sender) revert CallerNotAFactory(msg.sender, _poolFactory); _; } function mockInit() external { - __PoolContractsRegistry_init(); + __APoolContractsRegistry_init(); } function setDependencies(address contractsRegistry_, bytes memory data_) public override { diff --git a/contracts/mock/contracts-registry/pools/PoolMock.sol b/contracts/mock/contracts-registry/pools/PoolMock.sol index 8dddd9e6..6306eb87 100644 --- a/contracts/mock/contracts-registry/pools/PoolMock.sol +++ b/contracts/mock/contracts-registry/pools/PoolMock.sol @@ -1,11 +1,12 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.22; -import {AbstractDependant} from "../../../contracts-registry/AbstractDependant.sol"; +import {ADependant} from "../../../contracts-registry/ADependant.sol"; import {ContractsRegistryPoolMock} from "./ContractsRegistryPoolMock.sol"; -contract PoolMock is AbstractDependant { +contract PoolMock is ADependant { address public token; function setDependencies(address contractsRegistry_, bytes memory) public override dependant { diff --git a/contracts/mock/contracts-registry/pools/PoolUpgradeMock.sol b/contracts/mock/contracts-registry/pools/PoolUpgradeMock.sol index c6957a7d..a63a04d0 100644 --- a/contracts/mock/contracts-registry/pools/PoolUpgradeMock.sol +++ b/contracts/mock/contracts-registry/pools/PoolUpgradeMock.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.22; import {PoolMock} from "./PoolMock.sol"; diff --git a/contracts/mock/contracts-registry/pools/pool-factory/PoolFactoryMock.sol b/contracts/mock/contracts-registry/pools/pool-factory/PoolFactoryMock.sol index 3709178e..d67fb0a3 100644 --- a/contracts/mock/contracts-registry/pools/pool-factory/PoolFactoryMock.sol +++ b/contracts/mock/contracts-registry/pools/pool-factory/PoolFactoryMock.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.22; import {ContractsRegistryPoolMock} from "../ContractsRegistryPoolMock.sol"; import {PoolContractsRegistryMock} from "../PoolContractsRegistryMock.sol"; -import {AbstractPoolFactory} from "../../../../contracts-registry/pools/pool-factory/AbstractPoolFactory.sol"; +import {APoolFactory} from "../../../../contracts-registry/pools/pool-factory/APoolFactory.sol"; -contract PoolFactoryMock is AbstractPoolFactory { +contract PoolFactoryMock is APoolFactory { address public poolContractsRegistry; function setDependencies(address contractsRegistry_, bytes memory data_) public override { diff --git a/contracts/mock/contracts-registry/pools/presets/MultiOwnablePoolContractsRegistryMock.sol b/contracts/mock/contracts-registry/pools/presets/MultiOwnablePoolContractsRegistryMock.sol index dc3690a2..b53835e9 100644 --- a/contracts/mock/contracts-registry/pools/presets/MultiOwnablePoolContractsRegistryMock.sol +++ b/contracts/mock/contracts-registry/pools/presets/MultiOwnablePoolContractsRegistryMock.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; -import {MultiOwnablePoolContractsRegistry} from "../../../../contracts-registry/pools/presets/MultiOwnablePoolContractsRegistry.sol"; +import {AMultiOwnablePoolContractsRegistry} from "../../../../contracts-registry/pools/presets/AMultiOwnablePoolContractsRegistry.sol"; -contract MultiOwnablePoolContractsRegistryMock is MultiOwnablePoolContractsRegistry { +contract MultiOwnablePoolContractsRegistryMock is AMultiOwnablePoolContractsRegistry { function addProxyPool(string memory name_, address poolAddress_) public override { _addProxyPool(name_, poolAddress_); } diff --git a/contracts/mock/diamond/DummyFacetMock.sol b/contracts/mock/diamond/DummyFacetMock.sol index 39825da8..cf780aae 100644 --- a/contracts/mock/diamond/DummyFacetMock.sol +++ b/contracts/mock/diamond/DummyFacetMock.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {DummyStorageMock} from "./DummyStorageMock.sol"; diff --git a/contracts/mock/diamond/DummyInitMock.sol b/contracts/mock/diamond/DummyInitMock.sol index 5289d476..d0723054 100644 --- a/contracts/mock/diamond/DummyInitMock.sol +++ b/contracts/mock/diamond/DummyInitMock.sol @@ -1,11 +1,14 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {DummyFacetMock} from "./DummyFacetMock.sol"; contract DummyInitMock is DummyFacetMock { event Initialized(); + error InitError(); + function init() external { setDummyString("dummy facet initialized"); emit Initialized(); @@ -16,6 +19,6 @@ contract DummyInitMock is DummyFacetMock { } function initWithErrorMsg() external pure { - revert("DiamondInit: init error"); + revert InitError(); } } diff --git a/contracts/mock/diamond/DummyStorageMock.sol b/contracts/mock/diamond/DummyStorageMock.sol index d4618656..84966c15 100644 --- a/contracts/mock/diamond/DummyStorageMock.sol +++ b/contracts/mock/diamond/DummyStorageMock.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; contract DummyStorageMock { bytes32 public constant DUMMY_STORAGE_SLOT = keccak256("diamond.standard.dummyfacet.storage"); diff --git a/contracts/mock/diamond/OwnableDiamondMock.sol b/contracts/mock/diamond/OwnableDiamondMock.sol index 5424a6da..8661454f 100644 --- a/contracts/mock/diamond/OwnableDiamondMock.sol +++ b/contracts/mock/diamond/OwnableDiamondMock.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {OwnableDiamond} from "../../diamond/presets/OwnableDiamond.sol"; diff --git a/contracts/mock/diamond/access/DiamondAccessControlMock.sol b/contracts/mock/diamond/access/DiamondAccessControlMock.sol index eb12fe76..df6e291b 100644 --- a/contracts/mock/diamond/access/DiamondAccessControlMock.sol +++ b/contracts/mock/diamond/access/DiamondAccessControlMock.sol @@ -1,20 +1,21 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; -import {DiamondAccessControl} from "../../../diamond/access/access-control/DiamondAccessControl.sol"; +import {ADiamondAccessControl} from "../../../diamond/access/access-control/ADiamondAccessControl.sol"; -contract DiamondAccessControlMock is DiamondAccessControl { +contract DiamondAccessControlMock is ADiamondAccessControl { bytes32 public constant AGENT_ROLE = bytes32(uint256(0x01)); function __DiamondAccessControlDirect_init() external { - __DiamondAccessControl_init(); + __ADiamondAccessControl_init(); } function __DiamondAccessControlMock_init() external initializer(DIAMOND_ACCESS_CONTROL_STORAGE_SLOT) { - __DiamondAccessControl_init(); + __ADiamondAccessControl_init(); } function setRoleAdmin( diff --git a/contracts/mock/diamond/tokens/DiamondERC20Mock.sol b/contracts/mock/diamond/tokens/DiamondERC20Mock.sol index aa2097e8..7302ea30 100644 --- a/contracts/mock/diamond/tokens/DiamondERC20Mock.sol +++ b/contracts/mock/diamond/tokens/DiamondERC20Mock.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; -import "../../../diamond/tokens/ERC20/DiamondERC20.sol"; +import {DiamondERC20} from "../../../diamond/tokens/ERC20/DiamondERC20.sol"; contract DiamondERC20Mock is DiamondERC20 { constructor() { @@ -19,6 +20,18 @@ contract DiamondERC20Mock is DiamondERC20 { __DiamondERC20_init(name_, symbol_); } + function __DiamondERC20Mock_disableInit() external initializer(DIAMOND_ERC20_STORAGE_SLOT) { + _disableInitializers(DIAMOND_ERC20_STORAGE_SLOT); + } + + function __DiamondERC20Mock_reinitInit( + string memory name_, + string memory symbol_, + uint64 version_ + ) external initializer(DIAMOND_ERC20_STORAGE_SLOT) { + __DiamondERC20Mock_reinit(name_, symbol_, version_); + } + function mint(address to_, uint256 amount_) external { _mint(to_, amount_); } @@ -38,4 +51,22 @@ contract DiamondERC20Mock is DiamondERC20 { function disableInitializers() external { _disableInitializers(DIAMOND_ERC20_STORAGE_SLOT); } + + function enableInitializers(uint64 version_) external { + _getInitializableStorage() + .initializableStorage[DIAMOND_ERC20_STORAGE_SLOT] + .initialized = version_; + } + + function getInitializedVersion() external view returns (uint64) { + return _getInitializedVersion(DIAMOND_ERC20_STORAGE_SLOT); + } + + function __DiamondERC20Mock_reinit( + string memory name_, + string memory symbol_, + uint64 version_ + ) public reinitializer(DIAMOND_ERC20_STORAGE_SLOT, version_) { + __DiamondERC20_init(name_, symbol_); + } } diff --git a/contracts/mock/diamond/tokens/DiamondERC721Mock.sol b/contracts/mock/diamond/tokens/DiamondERC721Mock.sol index c454e955..f191fc15 100644 --- a/contracts/mock/diamond/tokens/DiamondERC721Mock.sol +++ b/contracts/mock/diamond/tokens/DiamondERC721Mock.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; -import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; import {DiamondERC721} from "../../../diamond/tokens/ERC721/DiamondERC721.sol"; contract DiamondERC721Mock is DiamondERC721 { - string baseUri; - bool replaceOwner; + string internal baseUri; + bool internal replaceOwner; constructor() { _disableInitializers(DIAMOND_ERC721_STORAGE_SLOT); @@ -25,6 +25,18 @@ contract DiamondERC721Mock is DiamondERC721 { __DiamondERC721_init(name_, symbol_); } + function __DiamondERC721Mock_disableInit() external initializer(DIAMOND_ERC721_STORAGE_SLOT) { + _disableInitializers(DIAMOND_ERC721_STORAGE_SLOT); + } + + function __DiamondERC721Mock_reinitInit( + string memory name_, + string memory symbol_, + uint64 version_ + ) external initializer(DIAMOND_ERC721_STORAGE_SLOT) { + __DiamondERC721Mock_reinit(name_, symbol_, version_); + } + function toggleReplaceOwner() external { replaceOwner = !replaceOwner; } @@ -49,40 +61,60 @@ contract DiamondERC721Mock is DiamondERC721 { safeTransferFrom(from_, to_, tokenId_); } - function beforeTokenTransfer(uint256 batchSize) external { - _beforeTokenTransfer(address(this), address(this), 1, batchSize); + function update(uint256 batchSize_) external { + _update(address(this), 1, batchSize_); } function disableInitializers() external { _disableInitializers(DIAMOND_ERC721_STORAGE_SLOT); } - function _baseURI() internal view override returns (string memory) { - super._baseURI(); - return baseUri; + function enableInitializers(uint64 version_) external { + _getInitializableStorage() + .initializableStorage[DIAMOND_ERC721_STORAGE_SLOT] + .initialized = version_; + } + + function getInitializedVersion() external view returns (uint64) { + return _getInitializedVersion(DIAMOND_ERC721_STORAGE_SLOT); + } + + function __DiamondERC721Mock_reinit( + string memory name_, + string memory symbol_, + uint64 version_ + ) public reinitializer(DIAMOND_ERC721_STORAGE_SLOT, version_) { + __DiamondERC721_init(name_, symbol_); } - function _beforeTokenTransfer( - address from_, + function _update( address to_, - uint256 firstTokenId_, + uint256 tokenId_, uint256 batchSize_ - ) internal override { + ) internal override returns (address) { if (replaceOwner) { - _getErc721Storage().owners[firstTokenId_] = address(this); + _getErc721Storage().owners[tokenId_] = address(this); + return address(this); } else { - super._beforeTokenTransfer(from_, to_, firstTokenId_, batchSize_); + return super._update(to_, tokenId_, batchSize_); } } + + function _baseURI() internal view override returns (string memory) { + super._baseURI(); + return baseUri; + } } contract NonERC721Receiver is IERC721Receiver { + error RevertingOnERC721Received(); + function onERC721Received( address, address, uint256, bytes calldata ) external pure override returns (bytes4) { - revert("ERC721Receiver: reverting onERC721Received"); + revert RevertingOnERC721Received(); } } diff --git a/contracts/mock/diamond/tokens/DiamondERC721NotReceiverMock.sol b/contracts/mock/diamond/tokens/DiamondERC721NotReceiverMock.sol new file mode 100644 index 00000000..bc2b7b52 --- /dev/null +++ b/contracts/mock/diamond/tokens/DiamondERC721NotReceiverMock.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +// solhint-disable +pragma solidity ^0.8.21; + +import {DiamondERC721Mock} from "./DiamondERC721Mock.sol"; + +contract DiamondERC721NotReceiverMock is DiamondERC721Mock { + function mockMint(address to_, uint256 tokenId_) external { + _mint(to_, tokenId_); + } + + function _checkOnERC721Received( + address, + address, + uint256, + bytes memory + ) internal pure override returns (bool) { + return false; + } +} diff --git a/contracts/mock/diamond/tokens/ERC721HolderMock.sol b/contracts/mock/diamond/tokens/ERC721HolderMock.sol new file mode 100644 index 00000000..c9f2054b --- /dev/null +++ b/contracts/mock/diamond/tokens/ERC721HolderMock.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +// solhint-disable +pragma solidity ^0.8.21; + +import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; + +contract ERC721HolderMock is ERC721Holder {} diff --git a/contracts/mock/finance/compound-rate-keeper/CompoundRateKeeperMock.sol b/contracts/mock/finance/compound-rate-keeper/CompoundRateKeeperMock.sol index 94373103..9b3646c5 100644 --- a/contracts/mock/finance/compound-rate-keeper/CompoundRateKeeperMock.sol +++ b/contracts/mock/finance/compound-rate-keeper/CompoundRateKeeperMock.sol @@ -1,11 +1,12 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {OwnableCompoundRateKeeper} from "../../../finance/compound-rate-keeper/presets/OwnableCompoundRateKeeper.sol"; contract CompoundRateKeeperMock is OwnableCompoundRateKeeper { function mockInit(uint256 capitalizationRate_, uint64 capitalizationPeriod_) external { - __CompoundRateKeeper_init(capitalizationRate_, capitalizationPeriod_); + __ACompoundRateKeeper_init(capitalizationRate_, capitalizationPeriod_); } function setCapitalizationRateAndPeriod( diff --git a/contracts/mock/finance/staking/AbstractStakingMock.sol b/contracts/mock/finance/staking/StakingMock.sol similarity index 78% rename from contracts/mock/finance/staking/AbstractStakingMock.sol rename to contracts/mock/finance/staking/StakingMock.sol index 0b1a3878..0c28f2d0 100644 --- a/contracts/mock/finance/staking/AbstractStakingMock.sol +++ b/contracts/mock/finance/staking/StakingMock.sol @@ -1,19 +1,20 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {Multicall} from "@openzeppelin/contracts/utils/Multicall.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {AbstractStaking} from "../../../finance/staking/AbstractStaking.sol"; +import {AStaking} from "../../../finance/staking/AStaking.sol"; -contract AbstractStakingMock is AbstractStaking, Multicall { - function __AbstractStakingMock_init( +contract StakingMock is AStaking, Multicall { + function __StakingMock_init( address sharesToken_, address rewardsToken_, uint256 rate_, uint256 stakingStartTime_ ) external initializer { - __AbstractStaking_init(sharesToken_, rewardsToken_, rate_, stakingStartTime_); + __AStaking_init(sharesToken_, rewardsToken_, rate_, stakingStartTime_); } function mockInit( @@ -22,7 +23,7 @@ contract AbstractStakingMock is AbstractStaking, Multicall { uint256 rate_, uint256 stakingStartTime_ ) external { - __AbstractStaking_init(sharesToken_, rewardsToken_, rate_, stakingStartTime_); + __AStaking_init(sharesToken_, rewardsToken_, rate_, stakingStartTime_); } function setStakingStartTime(uint256 stakingStartTime_) external { @@ -67,10 +68,10 @@ contract StakersFactory is Multicall { contract Staker { function stake(address stakingContract_, address token_, uint256 amount_) external { IERC20(token_).approve(stakingContract_, amount_); - AbstractStakingMock(stakingContract_).stake(amount_); + StakingMock(stakingContract_).stake(amount_); } function unstake(address stakingContract_, uint256 amount_) external { - AbstractStakingMock(stakingContract_).unstake(amount_); + StakingMock(stakingContract_).unstake(amount_); } } diff --git a/contracts/mock/finance/staking/AbstractValueDistributorMock.sol b/contracts/mock/finance/staking/ValueDistributorMock.sol similarity index 84% rename from contracts/mock/finance/staking/AbstractValueDistributorMock.sol rename to contracts/mock/finance/staking/ValueDistributorMock.sol index 58532dae..a69ddc54 100644 --- a/contracts/mock/finance/staking/AbstractValueDistributorMock.sol +++ b/contracts/mock/finance/staking/ValueDistributorMock.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {Multicall} from "@openzeppelin/contracts/utils/Multicall.sol"; -import {AbstractValueDistributor} from "../../../finance/staking/AbstractValueDistributor.sol"; +import {AValueDistributor} from "../../../finance/staking/AValueDistributor.sol"; import {DECIMAL} from "../../../utils/Globals.sol"; -contract AbstractValueDistributorMock is AbstractValueDistributor, Multicall { +contract ValueDistributorMock is AValueDistributor, Multicall { function addShares(address user_, uint256 amount_) external { _addShares(user_, amount_); } diff --git a/contracts/mock/finance/vesting/VestingMock.sol b/contracts/mock/finance/vesting/VestingMock.sol index ccadc7db..e4913cec 100644 --- a/contracts/mock/finance/vesting/VestingMock.sol +++ b/contracts/mock/finance/vesting/VestingMock.sol @@ -1,16 +1,17 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; -import {Vesting} from "../../../finance/vesting/Vesting.sol"; +import {AVesting} from "../../../finance/vesting/AVesting.sol"; import {ERC20Mock} from "../../tokens/ERC20Mock.sol"; -contract VestingMock is Vesting { +contract VestingMock is AVesting { function __VestingMock_init() public initializer { - __Vesting_init(); + __AVesting_init(); } function vestingInit() public { - __Vesting_init(); + __AVesting_init(); } function createSchedule(Schedule memory _schedule) public { diff --git a/contracts/mock/libs/arrays/ArrayHelperMock.sol b/contracts/mock/libs/arrays/ArrayHelperMock.sol index 60843240..cb3bb914 100644 --- a/contracts/mock/libs/arrays/ArrayHelperMock.sol +++ b/contracts/mock/libs/arrays/ArrayHelperMock.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {ArrayHelper} from "../../../libs/arrays/ArrayHelper.sol"; diff --git a/contracts/mock/libs/arrays/PaginatorMock.sol b/contracts/mock/libs/arrays/PaginatorMock.sol index 3d636555..5053d817 100644 --- a/contracts/mock/libs/arrays/PaginatorMock.sol +++ b/contracts/mock/libs/arrays/PaginatorMock.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; diff --git a/contracts/mock/libs/arrays/SetHelperMock.sol b/contracts/mock/libs/arrays/SetHelperMock.sol index 8a8cab7f..f21da3aa 100644 --- a/contracts/mock/libs/arrays/SetHelperMock.sol +++ b/contracts/mock/libs/arrays/SetHelperMock.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; diff --git a/contracts/mock/libs/bn/U512Mock.sol b/contracts/mock/libs/bn/U512Mock.sol index 9b9a4ef8..7412ff0c 100644 --- a/contracts/mock/libs/bn/U512Mock.sol +++ b/contracts/mock/libs/bn/U512Mock.sol @@ -1,10 +1,9 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; -import {call512} from "../../../libs/bn/U512.sol"; -import {uint512} from "../../../libs/bn/U512.sol"; +// Separate imports due to IntelliJ Solidity plugin issues +import {call512, uint512} from "../../../libs/bn/U512.sol"; import {U512} from "../../../libs/bn/U512.sol"; -// import "hardhat/console.sol"; contract U512Mock { using U512 for *; diff --git a/contracts/mock/libs/crypto/ECDSA256Mock.sol b/contracts/mock/libs/crypto/ECDSA256Mock.sol index f95457e5..3b0d0519 100644 --- a/contracts/mock/libs/crypto/ECDSA256Mock.sol +++ b/contracts/mock/libs/crypto/ECDSA256Mock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {ECDSA256} from "../../../libs/crypto/ECDSA256.sol"; diff --git a/contracts/mock/libs/crypto/ECDSA384Mock.sol b/contracts/mock/libs/crypto/ECDSA384Mock.sol index 8786b97d..b29c035f 100644 --- a/contracts/mock/libs/crypto/ECDSA384Mock.sol +++ b/contracts/mock/libs/crypto/ECDSA384Mock.sol @@ -1,7 +1,9 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {ECDSA384} from "../../../libs/crypto/ECDSA384.sol"; + +// Separate imports due to IntelliJ Solidity plugin issues import {uint512} from "../../../libs/bn/U512.sol"; import {U512} from "../../../libs/bn/U512.sol"; diff --git a/contracts/mock/libs/crypto/ECDSA512Mock.sol b/contracts/mock/libs/crypto/ECDSA512Mock.sol index 82a6d68e..c8ca9ec7 100644 --- a/contracts/mock/libs/crypto/ECDSA512Mock.sol +++ b/contracts/mock/libs/crypto/ECDSA512Mock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {ECDSA512} from "../../../libs/crypto/ECDSA512.sol"; diff --git a/contracts/mock/libs/crypto/RSASSAPSSMock.sol b/contracts/mock/libs/crypto/RSASSAPSSMock.sol index 7b59116e..7da55d23 100644 --- a/contracts/mock/libs/crypto/RSASSAPSSMock.sol +++ b/contracts/mock/libs/crypto/RSASSAPSSMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {RSASSAPSS} from "../../../libs/crypto/RSASSAPSS.sol"; diff --git a/contracts/mock/libs/data-structures/AvlTreeMock.sol b/contracts/mock/libs/data-structures/AvlTreeMock.sol index 66378e13..0d887067 100644 --- a/contracts/mock/libs/data-structures/AvlTreeMock.sol +++ b/contracts/mock/libs/data-structures/AvlTreeMock.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {TypeCaster} from "../../../libs/utils/TypeCaster.sol"; -import {Traversal, AvlTree} from "../../../libs/data-structures/AvlTree.sol"; +import {AvlTree, Traversal} from "../../../libs/data-structures/AvlTree.sol"; contract AvlTreeMock { using TypeCaster for *; diff --git a/contracts/mock/libs/data-structures/CartesianMerkleTreeMock.sol b/contracts/mock/libs/data-structures/CartesianMerkleTreeMock.sol index 1e177905..955ff07d 100644 --- a/contracts/mock/libs/data-structures/CartesianMerkleTreeMock.sol +++ b/contracts/mock/libs/data-structures/CartesianMerkleTreeMock.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; +// solhint-disable-previous-line one-contract-per-file import {CartesianMerkleTree} from "../../../libs/data-structures/CartesianMerkleTree.sol"; diff --git a/contracts/mock/libs/data-structures/DynamicSetMock.sol b/contracts/mock/libs/data-structures/DynamicSetMock.sol index 7fc0d157..fca6023a 100644 --- a/contracts/mock/libs/data-structures/DynamicSetMock.sol +++ b/contracts/mock/libs/data-structures/DynamicSetMock.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {DynamicSet} from "../../../libs/data-structures/DynamicSet.sol"; diff --git a/contracts/mock/libs/data-structures/IncrementalMerkleTreeMock.sol b/contracts/mock/libs/data-structures/IncrementalMerkleTreeMock.sol index 226fb0a2..c101196f 100644 --- a/contracts/mock/libs/data-structures/IncrementalMerkleTreeMock.sol +++ b/contracts/mock/libs/data-structures/IncrementalMerkleTreeMock.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {IncrementalMerkleTree} from "../../../libs/data-structures/IncrementalMerkleTree.sol"; diff --git a/contracts/mock/libs/data-structures/PriorityQueueMock.sol b/contracts/mock/libs/data-structures/PriorityQueueMock.sol index dc52234d..5aaef6ee 100644 --- a/contracts/mock/libs/data-structures/PriorityQueueMock.sol +++ b/contracts/mock/libs/data-structures/PriorityQueueMock.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {PriorityQueue} from "../../../libs/data-structures/PriorityQueue.sol"; diff --git a/contracts/mock/libs/data-structures/SparseMerkleTreeMock.sol b/contracts/mock/libs/data-structures/SparseMerkleTreeMock.sol index 2e3ad90a..74856773 100644 --- a/contracts/mock/libs/data-structures/SparseMerkleTreeMock.sol +++ b/contracts/mock/libs/data-structures/SparseMerkleTreeMock.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {SparseMerkleTree} from "../../../libs/data-structures/SparseMerkleTree.sol"; diff --git a/contracts/mock/libs/data-structures/memory/VectorMock.sol b/contracts/mock/libs/data-structures/memory/VectorMock.sol index 41c772d6..df595657 100644 --- a/contracts/mock/libs/data-structures/memory/VectorMock.sol +++ b/contracts/mock/libs/data-structures/memory/VectorMock.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {Vector} from "../../../../libs/data-structures/memory/Vector.sol"; import {TypeCaster} from "../../../../libs/utils/TypeCaster.sol"; @@ -18,7 +19,7 @@ contract VectorMock { Vector.UintVector memory vector1_ = Vector.newUint(); - require(vector1_.length() == 0); + if (vector1_.length() != 0) revert(); assembly { mstore( @@ -37,76 +38,76 @@ contract VectorMock { Vector.UintVector memory vector2_ = Vector.newUint(3); - require(vector2_.length() == 3); + if (vector2_.length() != 3) revert(); for (uint256 i = 0; i < vector2_.length(); i++) { - require(vector2_.at(i) == 0); + if (vector2_.at(i) != 0) revert(); } } function testArrayPush() external pure { Vector.UintVector memory vector_ = Vector.newUint(); - require(vector_._vector._allocation == 5); - require(vector_.length() == 0); + if (vector_._vector._allocation != 5) revert(); + if (vector_.length() != 0) revert(); vector_.push([uint256(1), 2, 3].asDynamic()); - require(vector_.length() == 3); + if (vector_.length() != 3) revert(); } function testPushAndPop() external pure { Vector.UintVector memory vector_ = Vector.newUint(); - require(vector_._vector._allocation == 5); - require(vector_.length() == 0); + if (vector_._vector._allocation != 5) revert(); + if (vector_.length() != 0) revert(); vector_.push(1); vector_.push(2); vector_.push(3); - require(vector_.length() == 3); + if (vector_.length() != 3) revert(); for (uint256 i = 0; i < vector_.length(); i++) { - require(vector_.at(i) == i + 1); + if (vector_.at(i) != i + 1) revert(); } vector_.pop(); - require(vector_.length() == 2); + if (vector_.length() != 2) revert(); vector_.push(0); - require(vector_.at(2) == 0); + if (vector_.at(2) != 0) revert(); } function testResize() external pure { Vector.UintVector memory vector_ = Vector.newUint(0); - require(vector_._vector._allocation == 1); - require(vector_.length() == 0); + if (vector_._vector._allocation != 1) revert(); + if (vector_.length() != 0) revert(); for (uint256 i = 0; i < 10; i++) { vector_.push(i); } - require(vector_._vector._allocation == 16); - require(vector_.length() == 10); + if (vector_._vector._allocation != 16) revert(); + if (vector_.length() != 10) revert(); uint256[] memory array_ = vector_.toArray(); - require(array_.length == 10); + if (array_.length != 10) revert(); for (uint256 i = 0; i < array_.length; i++) { - require(array_[i] == i); + if (array_[i] != i) revert(); } } function testResizeAndSet() external pure { Vector.UintVector memory vector1_ = Vector.newUint(1); - require(vector1_.length() == 1); - require(vector1_.at(0) == 0); + if (vector1_.length() != 1) revert(); + if (vector1_.at(0) != 0) revert(); for (uint256 i = 1; i < 50; i++) { vector1_.push(i); @@ -114,18 +115,18 @@ contract VectorMock { uint256[] memory array_ = vector1_.toArray(); - require(array_.length == 50); + if (array_.length != 50) revert(); Vector.UintVector memory vector2_ = Vector.newUint(array_); - require(vector2_.length() == 50); + if (vector2_.length() != 50) revert(); for (uint256 i = 0; i < 50; i++) { - require(vector2_.at(i) == i); + if (vector2_.at(i) != i) revert(); vector2_.set(i, 50 - i); - require(vector2_.at(i) == 50 - i); + if (vector2_.at(i) != 50 - i) revert(); } } @@ -150,7 +151,7 @@ contract VectorMock { function testUintFunctionality() external pure { Vector.UintVector memory vector_ = Vector.newUint(); - require(vector_.length() == 0); + if (vector_.length() != 0) revert(); vector_.push(1); vector_.set(0, 2); @@ -159,41 +160,41 @@ contract VectorMock { uint256[] memory array_ = vector_.toArray(); - require(array_.length == 2); - require(array_[0] == 2); - require(array_[1] == 3); + if (array_.length != 2) revert(); + if (array_[0] != 2) revert(); + if (array_[1] != 3) revert(); array_[0] = 10; - require(vector_.at(0) == 10); + if (vector_.at(0) != 10) revert(); vector_ = Vector.newUint(5); array_ = vector_.toArray(); - require(array_.length == 5); + if (array_.length != 5) revert(); vector_.push(0); vector_.pop(); array_ = vector_.toArray(); - require(array_.length == 5); + if (array_.length != 5) revert(); array_[array_.length - 1] = 1; vector_ = Vector.newUint(array_); - require(vector_.length() == 5); - require(vector_.at(vector_.length() - 1) == 1); + if (vector_.length() != 5) revert(); + if (vector_.at(vector_.length() - 1) != 1) revert(); vector_.push([uint256(1), 2, 3].asDynamic()); - require(vector_.length() == 8); + if (vector_.length() != 8) revert(); } function testBytes32Functionality() external pure { Vector.Bytes32Vector memory vector_ = Vector.newBytes32(); - require(vector_.length() == 0); + if (vector_.length() != 0) revert(); vector_.push(bytes32(uint256(1))); vector_.set(0, bytes32(uint256(2))); @@ -202,41 +203,41 @@ contract VectorMock { bytes32[] memory array_ = vector_.toArray(); - require(array_.length == 2); - require(array_[0] == bytes32(uint256(2))); - require(array_[1] == bytes32(uint256(3))); + if (array_.length != 2) revert(); + if (array_[0] != bytes32(uint256(2))) revert(); + if (array_[1] != bytes32(uint256(3))) revert(); array_[0] = bytes32(uint256(10)); - require(vector_.at(0) == bytes32(uint256(10))); + if (vector_.at(0) != bytes32(uint256(10))) revert(); vector_ = Vector.newBytes32(5); array_ = vector_.toArray(); - require(array_.length == 5); + if (array_.length != 5) revert(); vector_.push(0); vector_.pop(); array_ = vector_.toArray(); - require(array_.length == 5); + if (array_.length != 5) revert(); array_[array_.length - 1] = bytes32(uint256(1)); vector_ = Vector.newBytes32(array_); - require(vector_.length() == 5); - require(vector_.at(vector_.length() - 1) == bytes32(uint256(1))); + if (vector_.length() != 5) revert(); + if (vector_.at(vector_.length() - 1) != bytes32(uint256(1))) revert(); vector_.push([bytes32(uint256(5)), bytes32(uint256(4)), bytes32(uint256(3))].asDynamic()); - require(vector_.length() == 8); + if (vector_.length() != 8) revert(); } function testAddressFunctionality() external pure { Vector.AddressVector memory vector_ = Vector.newAddress(); - require(vector_.length() == 0); + if (vector_.length() != 0) revert(); vector_.push(address(1)); vector_.set(0, address(2)); @@ -245,34 +246,34 @@ contract VectorMock { address[] memory array_ = vector_.toArray(); - require(array_.length == 2); - require(array_[0] == address(2)); - require(array_[1] == address(3)); + if (array_.length != 2) revert(); + if (array_[0] != address(2)) revert(); + if (array_[1] != address(3)) revert(); array_[0] = address(10); - require(vector_.at(0) == address(10)); + if (vector_.at(0) != address(10)) revert(); vector_ = Vector.newAddress(5); array_ = vector_.toArray(); - require(array_.length == 5); + if (array_.length != 5) revert(); vector_.push(address(0)); vector_.pop(); array_ = vector_.toArray(); - require(array_.length == 5); + if (array_.length != 5) revert(); array_[array_.length - 1] = address(1); vector_ = Vector.newAddress(array_); - require(vector_.length() == 5); - require(vector_.at(vector_.length() - 1) == address(1)); + if (vector_.length() != 5) revert(); + if (vector_.at(vector_.length() - 1) != address(1)) revert(); vector_.push([address(5), address(4), address(3)].asDynamic()); - require(vector_.length() == 8); + if (vector_.length() != 8) revert(); } } diff --git a/contracts/mock/libs/utils/DecimalsConverterMock.sol b/contracts/mock/libs/utils/DecimalsConverterMock.sol index ca4eec24..e5809420 100644 --- a/contracts/mock/libs/utils/DecimalsConverterMock.sol +++ b/contracts/mock/libs/utils/DecimalsConverterMock.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {DecimalsConverter} from "../../../libs/utils/DecimalsConverter.sol"; -import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract DecimalsConverterMock { using DecimalsConverter for *; diff --git a/contracts/mock/libs/utils/MemoryUtilsMock.sol b/contracts/mock/libs/utils/MemoryUtilsMock.sol index 38e14f05..382c1ea2 100644 --- a/contracts/mock/libs/utils/MemoryUtilsMock.sol +++ b/contracts/mock/libs/utils/MemoryUtilsMock.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {MemoryUtils} from "../../../libs/utils/MemoryUtils.sol"; import {TypeCaster} from "../../../libs/utils/TypeCaster.sol"; @@ -8,45 +9,37 @@ contract MemoryUtilsMock { using TypeCaster for *; using MemoryUtils for *; + error BytesMemoryCopyError(bool ifDataEqual); + error Bytes32MemoryCopyError(bool ifDataEqual); + error BigMemoryError(bool ifDataEqual); + error PartialCopyError(bool ifDataEqual); + function testBytesMemoryCopy(bytes memory data_) external view { bytes memory someBytes_ = new bytes(data_.length); - require( - keccak256(data_) != keccak256(someBytes_), - "MemoryUtilsMock: testBytesMemoryCopy failed. Initial data and someBytes are equal" - ); + if (keccak256(data_) == keccak256(someBytes_)) revert BytesMemoryCopyError(true); someBytes_ = data_.copy(); - require( - keccak256(data_) == keccak256(someBytes_), - "MemoryUtilsMock: testBytesMemoryCopy failed. Initial data and someBytes are not equal" - ); + if (keccak256(data_) != keccak256(someBytes_)) revert BytesMemoryCopyError(false); } function testBytes32MemoryCopy(bytes32[] memory data_) external view { bytes32[] memory someBytes_ = new bytes32[](data_.length); - require( - keccak256(abi.encode(data_)) != keccak256(abi.encode(someBytes_)), - "MemoryUtilsMock: testBytes32MemoryCopy failed. Initial data and someBytes are equal" - ); + if (keccak256(abi.encode(data_)) == keccak256(abi.encode(someBytes_))) + revert Bytes32MemoryCopyError(true); someBytes_ = data_.copy(); - require( - keccak256(abi.encode(data_)) == keccak256(abi.encode(someBytes_)), - "MemoryUtilsMock: testBytes32MemoryCopy failed. Initial data and someBytes are not equal" - ); + if (keccak256(abi.encode(data_)) != keccak256(abi.encode(someBytes_))) + revert Bytes32MemoryCopyError(false); } function testUnsafeMemoryCopy(bytes memory data_) external view { bytes memory someBytes_ = new bytes(data_.length); - require( - keccak256(data_) != keccak256(someBytes_), - "MemoryUtilsMock: testBigMemory failed. Initial data and someBytes are equal" - ); + if (keccak256(data_) == keccak256(someBytes_)) revert BigMemoryError(true); MemoryUtils.unsafeCopy( MemoryUtils.getDataPointer(someBytes_), @@ -54,19 +47,13 @@ contract MemoryUtilsMock { someBytes_.length ); - require( - keccak256(data_) == keccak256(someBytes_), - "MemoryUtilsMock: testBigMemory failed. Initial data and someBytes are not equal" - ); + if (keccak256(data_) != keccak256(someBytes_)) revert BigMemoryError(false); } function testPartialCopy(bytes memory data_) external view { bytes memory someBytes_ = new bytes(data_.length / 2); - require( - keccak256(data_) != keccak256(someBytes_), - "MemoryUtilsMock: testPartialCopy failed. Initial data and someBytes are equal" - ); + if (keccak256(data_) == keccak256(someBytes_)) revert PartialCopyError(true); MemoryUtils.unsafeCopy( MemoryUtils.getDataPointer(someBytes_), @@ -75,10 +62,7 @@ contract MemoryUtilsMock { ); for (uint256 i = 0; i < someBytes_.length; i++) { - require( - someBytes_[i] == data_[i], - "MemoryUtilsMock: testPartialCopy failed. Initial data and someBytes are not equal" - ); + if (someBytes_[i] != data_[i]) revert PartialCopyError(false); } } diff --git a/contracts/mock/libs/utils/ReturnDataProxyMock.sol b/contracts/mock/libs/utils/ReturnDataProxyMock.sol index ace3f53e..f92ea284 100644 --- a/contracts/mock/libs/utils/ReturnDataProxyMock.sol +++ b/contracts/mock/libs/utils/ReturnDataProxyMock.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {ReturnDataProxy} from "../../../libs/utils/ReturnDataProxy.sol"; @@ -12,6 +13,8 @@ struct Entry { contract RawReturnMock { uint256 private _mirror; + error Test(); + receive() external payable {} function setMirror(uint256 mirror_) external { @@ -27,7 +30,7 @@ contract RawReturnMock { } function revertWithMessage() external pure { - revert("test"); + revert Test(); } function getEntry() external pure returns (Entry memory) { diff --git a/contracts/mock/libs/utils/TypeCasterMock.sol b/contracts/mock/libs/utils/TypeCasterMock.sol index 25ac1e2f..fedded63 100644 --- a/contracts/mock/libs/utils/TypeCasterMock.sol +++ b/contracts/mock/libs/utils/TypeCasterMock.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {TypeCaster} from "../../../libs/utils/TypeCaster.sol"; diff --git a/contracts/mock/libs/zkp/snarkjs/VerifierHelperMock.sol b/contracts/mock/libs/zkp/snarkjs/Groth16VerifierHelperMock.sol similarity index 77% rename from contracts/mock/libs/zkp/snarkjs/VerifierHelperMock.sol rename to contracts/mock/libs/zkp/snarkjs/Groth16VerifierHelperMock.sol index b23f7cba..51479b73 100644 --- a/contracts/mock/libs/zkp/snarkjs/VerifierHelperMock.sol +++ b/contracts/mock/libs/zkp/snarkjs/Groth16VerifierHelperMock.sol @@ -1,15 +1,16 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; -import {VerifierHelper} from "../../../../libs/zkp/snarkjs/VerifierHelper.sol"; +import {Groth16VerifierHelper} from "../../../../libs/zkp/Groth16VerifierHelper.sol"; -contract VerifierHelperMock { - using VerifierHelper for address; +contract Groth16VerifierHelperMock { + using Groth16VerifierHelper for address; function verifyProofStruct( address verifier_, uint256[] memory pubSignals_, - VerifierHelper.ProofPoints memory proofPoints_ + Groth16VerifierHelper.ProofPoints memory proofPoints_ ) external view returns (bool) { return verifier_.verifyProof(pubSignals_, proofPoints_); } @@ -27,7 +28,7 @@ contract VerifierHelperMock { function verifyProofStructSafe( address verifier_, uint256[] memory pubSignals_, - VerifierHelper.ProofPoints memory proofPoints_, + Groth16VerifierHelper.ProofPoints memory proofPoints_, uint256 pubSignalsCount_ ) external view returns (bool) { return verifier_.verifyProofSafe(pubSignals_, proofPoints_, pubSignalsCount_); diff --git a/contracts/mock/libs/zkp/snarkjs/VerifierMock.sol b/contracts/mock/libs/zkp/snarkjs/Groth16VerifierMock.sol similarity index 71% rename from contracts/mock/libs/zkp/snarkjs/VerifierMock.sol rename to contracts/mock/libs/zkp/snarkjs/Groth16VerifierMock.sol index 1e90f734..cbb07ab7 100644 --- a/contracts/mock/libs/zkp/snarkjs/VerifierMock.sol +++ b/contracts/mock/libs/zkp/snarkjs/Groth16VerifierMock.sol @@ -1,10 +1,13 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; -contract BaseVerifierMock { +contract BaseGroth16VerifierMock { bool public verifyResult; uint256[] public expectedInputs; + error InvalidInputs(); + constructor(bool verifyResult_, uint256[] memory expectedInputs_) { verifyResult = verifyResult_; expectedInputs = expectedInputs_; @@ -19,11 +22,11 @@ contract BaseVerifierMock { } } -contract Verifier2Mock is BaseVerifierMock { +contract Groth16Verifier2Mock is BaseGroth16VerifierMock { constructor( bool verifyResult_, uint256[] memory expectedInputs_ - ) BaseVerifierMock(verifyResult_, expectedInputs_) {} + ) BaseGroth16VerifierMock(verifyResult_, expectedInputs_) {} function verifyProof( uint256[2] memory, @@ -32,18 +35,18 @@ contract Verifier2Mock is BaseVerifierMock { uint256[2] memory inputs_ ) external view returns (bool) { for (uint256 i = 0; i < inputs_.length; i++) { - require(inputs_[i] == expectedInputs[i], "Verifier2Mock: invalid inputs"); + if (inputs_[i] != expectedInputs[i]) revert InvalidInputs(); } return verifyResult; } } -contract Verifier3Mock is BaseVerifierMock { +contract Groth16Verifier3Mock is BaseGroth16VerifierMock { constructor( bool verifyResult_, uint256[] memory expectedInputs_ - ) BaseVerifierMock(verifyResult_, expectedInputs_) {} + ) BaseGroth16VerifierMock(verifyResult_, expectedInputs_) {} function verifyProof( uint256[2] memory, @@ -52,7 +55,7 @@ contract Verifier3Mock is BaseVerifierMock { uint256[3] memory inputs_ ) external view returns (bool) { for (uint256 i = 0; i < inputs_.length; i++) { - require(inputs_[i] == expectedInputs[i], "Verifier3Mock: invalid inputs"); + if (inputs_[i] != expectedInputs[i]) revert InvalidInputs(); } return verifyResult; diff --git a/contracts/mock/oracles/uniswap-v2/UniswapV2FactoryMock.sol b/contracts/mock/oracles/uniswap-v2/UniswapV2FactoryMock.sol index 68be6692..5a39ccd9 100644 --- a/contracts/mock/oracles/uniswap-v2/UniswapV2FactoryMock.sol +++ b/contracts/mock/oracles/uniswap-v2/UniswapV2FactoryMock.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {IUniswapV2Pair} from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; diff --git a/contracts/mock/oracles/uniswap-v2/UniswapV2OracleMock.sol b/contracts/mock/oracles/uniswap-v2/UniswapV2OracleMock.sol index c32cd474..c1d0b2ac 100644 --- a/contracts/mock/oracles/uniswap-v2/UniswapV2OracleMock.sol +++ b/contracts/mock/oracles/uniswap-v2/UniswapV2OracleMock.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {UniswapV2Oracle} from "../../../oracles/UniswapV2Oracle.sol"; -import {UniswapV2PairMock} from "./UniswapV2PairMock.sol"; +import {AUniswapV2Oracle} from "../../../oracles/AUniswapV2Oracle.sol"; -contract UniswapV2OracleMock is UniswapV2Oracle { +contract UniswapV2OracleMock is AUniswapV2Oracle { using EnumerableSet for EnumerableSet.AddressSet; function __OracleV2Mock_init( diff --git a/contracts/mock/oracles/uniswap-v2/UniswapV2PairMock.sol b/contracts/mock/oracles/uniswap-v2/UniswapV2PairMock.sol index e21cb2bf..b1182bd0 100644 --- a/contracts/mock/oracles/uniswap-v2/UniswapV2PairMock.sol +++ b/contracts/mock/oracles/uniswap-v2/UniswapV2PairMock.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; contract UniswapV2PairMock { address public token0; diff --git a/contracts/mock/oracles/uniswap-v3/UniswapV3FactoryMock.sol b/contracts/mock/oracles/uniswap-v3/UniswapV3FactoryMock.sol index e5b927cb..311be0c8 100644 --- a/contracts/mock/oracles/uniswap-v3/UniswapV3FactoryMock.sol +++ b/contracts/mock/oracles/uniswap-v3/UniswapV3FactoryMock.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// solhint-disable pragma solidity >=0.5.0 <0.8.0; import {UniswapV3PoolMock} from "./UniswapV3PoolMock.sol"; diff --git a/contracts/mock/oracles/uniswap-v3/UniswapV3PoolMock.sol b/contracts/mock/oracles/uniswap-v3/UniswapV3PoolMock.sol index b89fe39c..0825b68c 100644 --- a/contracts/mock/oracles/uniswap-v3/UniswapV3PoolMock.sol +++ b/contracts/mock/oracles/uniswap-v3/UniswapV3PoolMock.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// solhint-disable pragma solidity >=0.5.0 <0.8.0; import {Oracle} from "@uniswap/v3-core/contracts/libraries/Oracle.sol"; diff --git a/contracts/mock/tokens/ERC20Mock.sol b/contracts/mock/tokens/ERC20Mock.sol index 544df5ca..b460fe88 100644 --- a/contracts/mock/tokens/ERC20Mock.sol +++ b/contracts/mock/tokens/ERC20Mock.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; diff --git a/contracts/mock/tokens/SBTMock.sol b/contracts/mock/tokens/SBTMock.sol index 5f2b882f..3ad7fd0a 100644 --- a/contracts/mock/tokens/SBTMock.sol +++ b/contracts/mock/tokens/SBTMock.sol @@ -1,15 +1,16 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; -import {SBT} from "./../../tokens/SBT.sol"; +import {ASBT} from "./../../tokens/ASBT.sol"; -contract SBTMock is SBT { +contract SBTMock is ASBT { function __SBTMock_init(string calldata name_, string calldata symbol_) external initializer { - __SBT_init(name_, symbol_); + __ASBT_init(name_, symbol_); } function mockInit(string calldata name_, string calldata symbol_) external { - __SBT_init(name_, symbol_); + __ASBT_init(name_, symbol_); } function mint(address to_, uint256 tokenId_) external { diff --git a/contracts/mock/utils/BlockGuardMock.sol b/contracts/mock/utils/BlockGuardMock.sol index ac5bf653..dea3d700 100644 --- a/contracts/mock/utils/BlockGuardMock.sol +++ b/contracts/mock/utils/BlockGuardMock.sol @@ -1,11 +1,12 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +// solhint-disable +pragma solidity ^0.8.21; -import "@openzeppelin/contracts/utils/Multicall.sol"; +import {Multicall} from "@openzeppelin/contracts/utils/Multicall.sol"; -import "../../utils/BlockGuard.sol"; +import {ABlockGuard} from "../../utils/ABlockGuard.sol"; -contract BlockGuardMock is Multicall, BlockGuard { +contract BlockGuardMock is Multicall, ABlockGuard { string public constant DEPOSIT_WITHDRAW_RESOURCE = "DEPOSIT_WITHDRAW"; string public constant LOCK_LOCK_RESOURCE = "LOCK_LOCK"; diff --git a/contracts/oracles/UniswapV2Oracle.sol b/contracts/oracles/AUniswapV2Oracle.sol similarity index 94% rename from contracts/oracles/UniswapV2Oracle.sol rename to contracts/oracles/AUniswapV2Oracle.sol index 520eace8..4fc5d06a 100644 --- a/contracts/oracles/UniswapV2Oracle.sol +++ b/contracts/oracles/AUniswapV2Oracle.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; @@ -20,7 +20,7 @@ import {ArrayHelper} from "../libs/arrays/ArrayHelper.sol"; * * From time to time `updatePrices()` function has to be called in order to calculate correct TWAP. */ -abstract contract UniswapV2Oracle is Initializable { +abstract contract AUniswapV2Oracle is Initializable { using EnumerableSet for EnumerableSet.AddressSet; using ArrayHelper for uint256[]; using Math for uint256; @@ -40,6 +40,11 @@ abstract contract UniswapV2Oracle is Initializable { mapping(address => address[]) private _paths; mapping(address => PairInfo) private _pairInfos; + error InvalidPath(address tokenIn, uint256 pathLength); + error PathAlreadyRegistered(address tokenIn); + error PairDoesNotExist(address token1, address token2); + error TimeWindowIsZero(); + /** * @notice Constructor * @param uniswapV2Factory_ the Uniswap V2 factory @@ -96,7 +101,7 @@ abstract contract UniswapV2Oracle is Initializable { address[] storage path = _paths[tokenIn_]; uint256 pathLength_ = path.length; - require(pathLength_ > 1, "UniswapV2Oracle: invalid path"); + if (pathLength_ < 2) revert InvalidPath(tokenIn_, pathLength_); address tokenOut_ = path[pathLength_ - 1]; @@ -163,7 +168,7 @@ abstract contract UniswapV2Oracle is Initializable { * @param newTimeWindow_ the new time window value in seconds */ function _setTimeWindow(uint256 newTimeWindow_) internal { - require(newTimeWindow_ > 0, "UniswapV2Oracle: time window can't be 0"); + if (newTimeWindow_ == 0) revert TimeWindowIsZero(); timeWindow = newTimeWindow_; } @@ -177,16 +182,15 @@ abstract contract UniswapV2Oracle is Initializable { for (uint256 i = 0; i < numberOfPaths_; i++) { uint256 pathLength_ = paths_[i].length; - - require(pathLength_ >= 2, "UniswapV2Oracle: path must be longer than 2"); - address tokenIn_ = paths_[i][0]; - require(_paths[tokenIn_].length == 0, "UniswapV2Oracle: path already registered"); + if (pathLength_ < 2) revert InvalidPath(tokenIn_, pathLength_); + if (_paths[tokenIn_].length != 0) revert PathAlreadyRegistered(tokenIn_); for (uint256 j = 0; j < pathLength_ - 1; j++) { (bool exists_, address pair_) = _pairExists(paths_[i][j], paths_[i][j + 1]); - require(exists_, "UniswapV2Oracle: uniswap pair doesn't exist"); + + if (!exists_) revert PairDoesNotExist(paths_[i][j], paths_[i][j + 1]); _pairs.add(pair_); _pairInfos[pair_].refs++; diff --git a/contracts/oracles/UniswapV3Oracle.sol b/contracts/oracles/UniswapV3Oracle.sol index dc70d7b4..4e6cc7ec 100644 --- a/contracts/oracles/UniswapV3Oracle.sol +++ b/contracts/oracles/UniswapV3Oracle.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT +// solhint-disable pragma solidity >=0.5.0 <0.8.0; import {IUniswapV3Factory} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol"; -import {IUniswapV3Pool} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; import {FullMath} from "@uniswap/v3-core/contracts/libraries/FullMath.sol"; import {OracleLibrary} from "@uniswap/v3-periphery/contracts/libraries/OracleLibrary.sol"; diff --git a/contracts/proxy/adminable/AdminableProxy.sol b/contracts/proxy/adminable/AdminableProxy.sol new file mode 100644 index 00000000..2e468516 --- /dev/null +++ b/contracts/proxy/adminable/AdminableProxy.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +// Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.2/contracts/proxy/transparent/TransparentUpgradeableProxy.sol + +pragma solidity ^0.8.22; + +import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; + +import {IAdminableProxy} from "../../interfaces/proxy/IAdminableProxy.sol"; + +/** + * @notice This contract implements a proxy that is upgradeable by an admin. + * + * The implementation of this contract is based on OpenZeppelin's TransparentUpgradeableProxy. + * The main change is in the constructor. While the original contract deploys an instance of ProxyAdmin + * for every proxy, this implementation simply sets the specified address as the admin. + * Additionally, an implementation function has been added. + * + * For more information about proxy logic, please refer to the OpenZeppelin documentation. + */ +contract AdminableProxy is ERC1967Proxy { + // solhint-disable-previous-line immutable-vars-naming + address private immutable _ADMIN; + + error ProxyDeniedAdminAccess(); + + constructor( + address logic_, + address admin_, + bytes memory data_ + ) payable ERC1967Proxy(logic_, data_) { + _ADMIN = admin_; + ERC1967Utils.changeAdmin(admin_); + } + + function _fallback() internal virtual override { + if (msg.sender != _ADMIN) { + super._fallback(); + } + + bytes4 selector_ = msg.sig; + + if (selector_ == IAdminableProxy.upgradeToAndCall.selector) { + _dispatchUpgradeToAndCall(); + } else if (selector_ == IAdminableProxy.implementation.selector) { + bytes memory returndata_ = _dispatchImplementation(); + + assembly { + return(add(returndata_, 0x20), mload(returndata_)) + } + } else { + revert ProxyDeniedAdminAccess(); + } + } + + function _dispatchUpgradeToAndCall() private { + (address newImplementation_, bytes memory data_) = abi.decode( + msg.data[4:], + (address, bytes) + ); + ERC1967Utils.upgradeToAndCall(newImplementation_, data_); + } + + function _dispatchImplementation() private view returns (bytes memory) { + return abi.encode(_implementation()); + } +} diff --git a/contracts/proxy/adminable/AdminableProxyUpgrader.sol b/contracts/proxy/adminable/AdminableProxyUpgrader.sol new file mode 100644 index 00000000..fe9b66dd --- /dev/null +++ b/contracts/proxy/adminable/AdminableProxyUpgrader.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import {APermanentOwnable} from "../../access/APermanentOwnable.sol"; +import {IAdminableProxy} from "./AdminableProxy.sol"; + +/** + * @notice The proxies module + * + * This is the lightweight helper contract that may be used as a AdminableProxy admin. + */ +contract AdminableProxyUpgrader is APermanentOwnable { + constructor() APermanentOwnable(msg.sender) {} + + /** + * @notice The function to upgrade the implementation contract + * @dev an attempt to upgrade a non-proxy contract will result in revert + * @param what_ the proxy contract to upgrade + * @param to_ the new implementation contract + * @param data_ arbitrary data the proxy will be called with after the upgrade + */ + function upgrade(address what_, address to_, bytes calldata data_) external virtual onlyOwner { + IAdminableProxy(payable(what_)).upgradeToAndCall(to_, data_); + } + + /** + * @notice The function to get the address of the proxy implementation + * @dev an attempt to get implementation from a non-proxy contract will result in revert + * @param what_ the proxy contract to observe + * @return the implementation address + */ + function getImplementation(address what_) public view virtual returns (address) { + return IAdminableProxy(what_).implementation(); + } +} diff --git a/contracts/proxy/beacon/ProxyBeacon.sol b/contracts/proxy/beacon/ProxyBeacon.sol index 1f67cdb8..958075f4 100644 --- a/contracts/proxy/beacon/ProxyBeacon.sol +++ b/contracts/proxy/beacon/ProxyBeacon.sol @@ -1,31 +1,31 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {IBeacon} from "@openzeppelin/contracts/proxy/beacon/IBeacon.sol"; -import {Address} from "@openzeppelin/contracts/utils/Address.sol"; -import {PermanentOwnable} from "../../access/PermanentOwnable.sol"; +import {APermanentOwnable} from "../../access/APermanentOwnable.sol"; /** * @notice The proxies module * * This is a lightweight utility ProxyBeacon contract that may be used as a beacon that BeaconProxies point to. */ -contract ProxyBeacon is IBeacon, PermanentOwnable { - using Address for address; - - constructor() PermanentOwnable(msg.sender) {} +contract ProxyBeacon is IBeacon, APermanentOwnable { + constructor() APermanentOwnable(msg.sender) {} address private _implementation; event Upgraded(address implementation); + error NewImplementationNotAContract(address newImplementation); + /** * @notice The function to upgrade to implementation contract * @param newImplementation_ the new implementation */ function upgradeTo(address newImplementation_) external virtual onlyOwner { - require(newImplementation_.isContract(), "ProxyBeacon: not a contract"); + if (newImplementation_.code.length == 0) + revert NewImplementationNotAContract(newImplementation_); _implementation = newImplementation_; diff --git a/contracts/proxy/beacon/PublicBeaconProxy.sol b/contracts/proxy/beacon/PublicBeaconProxy.sol index 85977e22..8a653c2e 100644 --- a/contracts/proxy/beacon/PublicBeaconProxy.sol +++ b/contracts/proxy/beacon/PublicBeaconProxy.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.22; import {BeaconProxy} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; diff --git a/contracts/proxy/transparent/TransparentProxyUpgrader.sol b/contracts/proxy/transparent/TransparentProxyUpgrader.sol deleted file mode 100644 index b3c09535..00000000 --- a/contracts/proxy/transparent/TransparentProxyUpgrader.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -import {ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import {Address} from "@openzeppelin/contracts/utils/Address.sol"; - -import {PermanentOwnable} from "../../access/PermanentOwnable.sol"; - -/** - * @notice The proxies module - * - * This is the lightweight helper contract that may be used as a TransparentProxy admin. - */ -contract TransparentProxyUpgrader is PermanentOwnable { - using Address for address; - - constructor() PermanentOwnable(msg.sender) {} - - /** - * @notice The function to upgrade the implementation contract - * @param what_ the proxy contract to upgrade - * @param to_ the new implementation contract - * @param data_ arbitrary data the proxy will be called with after the upgrade - */ - function upgrade(address what_, address to_, bytes calldata data_) external virtual onlyOwner { - if (data_.length > 0) { - ITransparentUpgradeableProxy(payable(what_)).upgradeToAndCall(to_, data_); - } else { - ITransparentUpgradeableProxy(payable(what_)).upgradeTo(to_); - } - } - - /** - * @notice The function to get the address of the proxy implementation - * @param what_ the proxy contract to observe - * @return the implementation address - */ - function getImplementation(address what_) public view virtual returns (address) { - // bytes4(keccak256("implementation()")) == 0x5c60da1b - (bool success_, bytes memory returndata_) = address(what_).staticcall(hex"5c60da1b"); - - require(success_, "TransparentProxyUpgrader: not a proxy"); - - return abi.decode(returndata_, (address)); - } -} diff --git a/contracts/tokens/SBT.sol b/contracts/tokens/ASBT.sol similarity index 92% rename from contracts/tokens/SBT.sol rename to contracts/tokens/ASBT.sol index bde38d9f..6691a805 100644 --- a/contracts/tokens/SBT.sol +++ b/contracts/tokens/ASBT.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import {ERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol"; import {IERC721Metadata} from "@openzeppelin/contracts/interfaces/IERC721Metadata.sol"; @@ -16,7 +16,7 @@ import {ISBT} from "../interfaces/tokens/ISBT.sol"; * * The contract is compatible with Metamask and Opensea. */ -abstract contract SBT is ISBT, ERC165Upgradeable { +abstract contract ASBT is ISBT, ERC165Upgradeable { using Strings for uint256; using EnumerableSet for EnumerableSet.UintSet; @@ -29,12 +29,16 @@ abstract contract SBT is ISBT, ERC165Upgradeable { string private _baseURI; mapping(uint256 => string) private _tokenURIs; + error ReceiverIsZeroAddress(); + error TokenAlreadyExists(uint256 tokenId); + error TokenDoesNotExist(uint256 tokenId); + /** * @notice The constructor * @param name_ the name of the contract (can't be changed) * @param symbol_ the symbol of the contract (can't be changed) */ - function __SBT_init(string memory name_, string memory symbol_) internal onlyInitializing { + function __ASBT_init(string memory name_, string memory symbol_) internal onlyInitializing { _name = name_; _symbol = symbol_; } @@ -156,8 +160,8 @@ abstract contract SBT is ISBT, ERC165Upgradeable { * @param tokenId_ the token to mint */ function _mint(address to_, uint256 tokenId_) internal virtual { - require(to_ != address(0), "SBT: address(0) receiver"); - require(!tokenExists(tokenId_), "SBT: token already exists"); + if (to_ == address(0)) revert ReceiverIsZeroAddress(); + if (tokenExists(tokenId_)) revert TokenAlreadyExists(tokenId_); _beforeTokenAction(to_, tokenId_); @@ -173,7 +177,8 @@ abstract contract SBT is ISBT, ERC165Upgradeable { */ function _burn(uint256 tokenId_) internal virtual { address owner_ = _ownerOf(tokenId_); - require(owner_ != address(0), "SBT: token doesn't exist"); + + if (owner_ == address(0)) revert TokenDoesNotExist(tokenId_); _beforeTokenAction(address(0), tokenId_); @@ -191,7 +196,7 @@ abstract contract SBT is ISBT, ERC165Upgradeable { * @param tokenURI_ the URI to be set */ function _setTokenURI(uint256 tokenId_, string memory tokenURI_) internal virtual { - require(tokenExists(tokenId_), "SBT: token doesn't exist"); + if (!tokenExists(tokenId_)) revert TokenDoesNotExist(tokenId_); _tokenURIs[tokenId_] = tokenURI_; } diff --git a/contracts/utils/BlockGuard.sol b/contracts/utils/ABlockGuard.sol similarity index 91% rename from contracts/utils/BlockGuard.sol rename to contracts/utils/ABlockGuard.sol index 4d59ef7f..1e40f94a 100644 --- a/contracts/utils/BlockGuard.sol +++ b/contracts/utils/ABlockGuard.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @notice The BlockGuard module @@ -21,9 +21,11 @@ pragma solidity ^0.8.4; * } * ``` */ -abstract contract BlockGuard { +abstract contract ABlockGuard { mapping(string => mapping(address => uint256)) private _lockedInBlocks; + error BlockGuardLocked(string resource, address key); + modifier lockBlock(string memory resource_, address key_) { _lockBlock(resource_, key_); _; @@ -55,7 +57,8 @@ abstract contract BlockGuard { * @param key_ the key of the resource (the caller) */ function _checkBlock(string memory resource_, address key_) internal view { - require(_lockedInBlocks[resource_][key_] != _getBlockNumber(), "BlockGuard: locked"); + if (_lockedInBlocks[resource_][key_] == _getBlockNumber()) + revert BlockGuardLocked(resource_, key_); } /** diff --git a/contracts/utils/Globals.sol b/contracts/utils/Globals.sol index 0aa9fe83..7c293c10 100644 --- a/contracts/utils/Globals.sol +++ b/contracts/utils/Globals.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; uint256 constant PRECISION = 10 ** 25; uint256 constant DECIMAL = 10 ** 18; diff --git a/hardhat.config.ts b/hardhat.config.ts index 7bed7690..09002180 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -26,7 +26,7 @@ const config: HardhatUserConfig = { solidity: { compilers: [ { - version: "0.8.20", + version: "0.8.22", settings: { optimizer: { enabled: true, @@ -55,7 +55,7 @@ const config: HardhatUserConfig = { contractSizer: { alphaSort: false, disambiguatePaths: false, - // runOnCompile: true, + runOnCompile: true, strict: false, }, gasReporter: { diff --git a/package-lock.json b/package-lock.json index 5d1d01ff..54a5d1f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { "name": "@solarity/solidity-lib", - "version": "2.7.17", + "version": "3.0.0-rc.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@solarity/solidity-lib", - "version": "2.7.17", + "version": "3.0.0-rc.0", "license": "MIT", "dependencies": { - "@openzeppelin/contracts": "4.9.6", - "@openzeppelin/contracts-upgradeable": "4.9.6", + "@openzeppelin/contracts": "5.2.0", + "@openzeppelin/contracts-upgradeable": "5.2.0", "@uniswap/v2-core": "1.0.1", "@uniswap/v2-periphery": "1.1.0-beta.0", "@uniswap/v3-core": "1.0.1", @@ -22,33 +22,32 @@ "@nomicfoundation/hardhat-chai-matchers": "^2.0.8", "@nomicfoundation/hardhat-ethers": "^3.0.8", "@nomicfoundation/hardhat-network-helpers": "^1.0.12", - "@solarity/hardhat-markup": "^1.0.8", + "@solarity/hardhat-markup": "^1.0.9", "@typechain/ethers-v6": "^0.5.1", "@typechain/hardhat": "^9.1.0", - "@types/chai": "^4.3.16", + "@types/chai": "^4.3.20", "@types/mocha": "^10.0.10", - "@types/node": "^18.16.0", + "@types/node": "^22.13.5", "bignumber.js": "^9.1.2", "chai": "^4.5.0", "circomlibjs": "^0.1.7", "dotenv": "^16.4.7", - "ethers": "^6.13.4", - "hardhat": "^2.22.0", + "ethers": "^6.13.5", + "hardhat": "^2.22.18", "hardhat-contract-sizer": "^2.10.0", "hardhat-gas-reporter": "^2.2.2", "husky": "^9.1.7", - "merkletreejs": "^0.4.0", - "mocha": "^11.0.1", + "merkletreejs": "^0.4.1", "mock-local-storage": "^1.1.24", - "prettier": "^3.4.2", - "prettier-plugin-solidity": "^1.4.1", - "solhint": "^5.0.3", + "prettier": "^3.5.2", + "prettier-plugin-solidity": "^1.4.2", + "solhint": "^5.0.5", "solhint-plugin-prettier": "^0.1.0", "solidity-coverage": "^0.8.14", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", "typechain": "^8.3.2", - "typescript": "^5.7.0" + "typescript": "^5.7.3" } }, "node_modules/@adraffy/ens-normalize": { @@ -1461,14 +1460,17 @@ } }, "node_modules/@openzeppelin/contracts": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.9.6.tgz", - "integrity": "sha512-xSmezSupL+y9VkHZJGDoCBpmnB2ogM13ccaYDWqJTfS3dbuHkgjuwDFUmaFauBCboQMGB/S5UqUl2y54X99BmA==" + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-5.2.0.tgz", + "integrity": "sha512-bxjNie5z89W1Ea0NZLZluFh8PrFNn9DH8DQlujEok2yjsOlraUPKID5p1Wk3qdNbf6XkQ1Os2RvfiHrrXLHWKA==" }, "node_modules/@openzeppelin/contracts-upgradeable": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.6.tgz", - "integrity": "sha512-m4iHazOsOCv1DgM7eD7GupTJ+NFVujRZt1wzddDPSVGpWdKq1SKkla5htKG7+IS4d2XOCtzkUNwRZ7Vq5aEUMA==" + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-5.2.0.tgz", + "integrity": "sha512-mZIu9oa4tQTlGiOJHk6D3LdJlqFqF6oNOSn6S6UVJtzfs9UsY9/dhMEbAVTwElxUtJnjpf6yA062+oBp+eOyPg==", + "peerDependencies": { + "@openzeppelin/contracts": "5.2.0" + } }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", @@ -1720,31 +1722,25 @@ } }, "node_modules/@solarity/hardhat-markup": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@solarity/hardhat-markup/-/hardhat-markup-1.0.8.tgz", - "integrity": "sha512-FYgm/G6oJfE/zY4zDRqyhYzZzdf9Jolri94G7MxuiaHLAGWX82AhiVNhImyNGOHUE3hk1x454qJa7OaHYVkvvg==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@solarity/hardhat-markup/-/hardhat-markup-1.0.9.tgz", + "integrity": "sha512-ddjAnoSKyJD4aoyj2L0GOe4bbRdggvMe6FP80mK8+huzaUefKlaIhQ7ub8QQ8c8S+KJlnkKcx1OLKm7PI44hmA==", "dev": true, "dependencies": { - "json2md": "2.0.1", + "json2md": "2.0.2", "lodash": "4.17.21", - "prettier": "3.2.5", - "prettier-plugin-solidity": "1.3.1", - "solidity-ast": "0.4.55" + "prettier": "3.5.1", + "prettier-plugin-solidity": "1.4.2", + "solidity-ast": "0.4.59" }, "peerDependencies": { "hardhat": "^2.10.0" } }, - "node_modules/@solarity/hardhat-markup/node_modules/@solidity-parser/parser": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.17.0.tgz", - "integrity": "sha512-Nko8R0/kUo391jsEHHxrGM07QFdnPGvlmox4rmH0kNiNAashItAilhy4Mv4pK5gQmW5f4sXAF58fwJbmlkGcVw==", - "dev": true - }, "node_modules/@solarity/hardhat-markup/node_modules/prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.1.tgz", + "integrity": "sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -1756,35 +1752,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/@solarity/hardhat-markup/node_modules/prettier-plugin-solidity": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.3.1.tgz", - "integrity": "sha512-MN4OP5I2gHAzHZG1wcuJl0FsLS3c4Cc5494bbg+6oQWBPuEamjwDvmGfFMZ6NFzsh3Efd9UUxeT7ImgjNH4ozA==", - "dev": true, - "dependencies": { - "@solidity-parser/parser": "^0.17.0", - "semver": "^7.5.4", - "solidity-comments-extractor": "^0.0.8" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "prettier": ">=2.3.0" - } - }, - "node_modules/@solarity/hardhat-markup/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@solidity-parser/parser": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.19.0.tgz", @@ -1916,12 +1883,12 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.19.74", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.74.tgz", - "integrity": "sha512-HMwEkkifei3L605gFdV+/UwtpxP6JSzM+xFk2Ia6DNFSwSVBRh9qp5Tgf4lNFOMfPVuU0WnkcWpXZpgn5ufO4A==", + "version": "22.13.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.5.tgz", + "integrity": "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==", "dev": true, "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.20.0" } }, "node_modules/@types/pbkdf2": { @@ -2237,22 +2204,6 @@ "node": ">=6" } }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -2262,47 +2213,6 @@ "node": ">=8" } }, - "node_modules/array.prototype.findlast": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", - "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -2333,15 +2243,6 @@ "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", "dev": true }, - "node_modules/async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2357,21 +2258,6 @@ "node": ">= 4.0.0" } }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/axios": { "version": "1.7.9", "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", @@ -2644,28 +2530,10 @@ "node": ">=14.16" } }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/call-bind-apply-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", - "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, "dependencies": { "es-errors": "^1.3.0", @@ -2675,22 +2543,6 @@ "node": ">= 0.4" } }, - "node_modules/call-bound": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", - "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", - "dev": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2910,17 +2762,14 @@ } }, "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "dependencies": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", + "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" } }, "node_modules/color-convert": { @@ -3211,57 +3060,6 @@ "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", "dev": true }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/death": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/death/-/death-1.1.0.tgz", @@ -3360,40 +3158,6 @@ "node": ">=10" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -3541,71 +3305,6 @@ "is-arrayish": "^0.2.1" } }, - "node_modules/es-abstract": { - "version": "1.23.9", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", - "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.0", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-regex": "^1.2.1", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.0", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.3", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.18" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -3651,32 +3350,6 @@ "node": ">= 0.4" } }, - "node_modules/es-shim-unscopables": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", - "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", - "dev": true, - "dependencies": { - "hasown": "^2.0.0" - } - }, - "node_modules/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", - "dev": true, - "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -3996,9 +3669,9 @@ ] }, "node_modules/fastq": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", - "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", + "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -4084,28 +3757,13 @@ } } }, - "node_modules/for-each": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.4.tgz", - "integrity": "sha512-kKaIINnFpzW6ffJNDjjyjrk21BkDx38c0xa/klsT8VzLCaMEefv4ZTacrcVR4DmgTeBra++jMDAfS/tS799YDw==", - "dev": true, - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { @@ -4116,13 +3774,14 @@ } }, "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" }, "engines": { @@ -4188,35 +3847,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -4236,17 +3866,17 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", - "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, "dependencies": { - "call-bind-apply-helpers": "^1.0.1", + "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "get-proto": "^1.0.0", + "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", @@ -4284,23 +3914,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-symbol-description": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/ghost-testrpc": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/ghost-testrpc/-/ghost-testrpc-0.0.2.tgz", @@ -4465,26 +4078,10 @@ "which": "bin/which" } }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/globby": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", - "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", + "node_modules/globby": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", + "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", "dev": true, "dependencies": { "@types/glob": "^7.1.1", @@ -4831,17 +4428,6 @@ "@scure/base": "~1.1.0" } }, - "node_modules/hardhat/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, "node_modules/hardhat/node_modules/ethereum-cryptography": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz", @@ -4868,26 +4454,6 @@ "node": ">=6 <7 || >=8" } }, - "node_modules/hardhat/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/hardhat/node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -4897,104 +4463,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/hardhat/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/hardhat/node_modules/mocha": { - "version": "10.8.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", - "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.3", - "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", - "debug": "^4.3.5", - "diff": "^5.2.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^8.1.0", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", - "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/hardhat/node_modules/mocha/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/hardhat/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/hardhat/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/hardhat/node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -5025,45 +4493,6 @@ } } }, - "node_modules/hardhat/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/hardhat/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5073,33 +4502,6 @@ "node": ">=8" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "dev": true, - "dependencies": { - "dunder-proto": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -5287,9 +4689,9 @@ "dev": true }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "dependencies": { "parent-module": "^1.0.0", @@ -5312,9 +4714,9 @@ } }, "node_modules/indento": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/indento/-/indento-1.1.13.tgz", - "integrity": "sha512-YZWk3mreBEM7sBPddsiQnW9Z8SGg/gNpFfscJq00HCDS7pxcQWWWMSVKJU7YkTRyDu1Zv2s8zaK8gQWKmCXHlg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/indento/-/indento-1.1.14.tgz", + "integrity": "sha512-K4cK97v4M/ucCAbe3LUpg994folYL0WnEiCFxHXAIowKLbBb/Ahiazkz3Ao5gRar4i9pDr3imcpq4suOu0FbNw==", "dev": true }, "node_modules/inflight": { @@ -5340,20 +4742,6 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/interpret": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", @@ -5372,63 +4760,12 @@ "fp-ts": "^1.0.0" } }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, - "node_modules/is-async-function": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", - "dev": true, - "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -5441,67 +4778,6 @@ "node": ">=8" } }, - "node_modules/is-boolean-object": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.1.tgz", - "integrity": "sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -5511,21 +4787,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -5535,24 +4796,6 @@ "node": ">=8" } }, - "node_modules/is-generator-function": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", - "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "get-proto": "^1.0.0", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -5575,18 +4818,6 @@ "npm": ">=3" } }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -5596,22 +4827,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -5621,99 +4836,6 @@ "node": ">=8" } }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -5726,55 +4848,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.0.tgz", - "integrity": "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -5863,9 +4936,9 @@ } }, "node_modules/json2md": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/json2md/-/json2md-2.0.1.tgz", - "integrity": "sha512-VbwmZ83qmUfKBS2pUOHlzNKEZFPBeJSbzEok3trMYyboZUgdHNn1XZfc1uT8UZs1GHCrmRUBXCfqw4YmmQuOhw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/json2md/-/json2md-2.0.2.tgz", + "integrity": "sha512-bPXMyPSZRsN2Xh5tNWA4RjREnYmDK2GFO8eYOTbHSfVPfrFaqzIqFWN3gdf1bya65cm9Mf2PBSwvM7sWGEeuVA==", "dev": true, "dependencies": { "indento": "^1.1.13" @@ -6265,9 +5338,9 @@ } }, "node_modules/mocha": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz", - "integrity": "sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==", + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", "dev": true, "dependencies": { "ansi-colors": "^4.1.3", @@ -6277,7 +5350,7 @@ "diff": "^5.2.0", "escape-string-regexp": "^4.0.0", "find-up": "^5.0.0", - "glob": "^10.4.5", + "glob": "^8.1.0", "he": "^1.2.0", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", @@ -6287,8 +5360,8 @@ "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", "workerpool": "^6.5.1", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", "yargs-unparser": "^2.0.0" }, "bin": { @@ -6296,7 +5369,7 @@ "mocha": "bin/mocha.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">= 14.0.0" } }, "node_modules/mocha/node_modules/chokidar": { @@ -6323,6 +5396,26 @@ "fsevents": "~2.3.2" } }, + "node_modules/mocha/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/mocha/node_modules/minimatch": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", @@ -6463,53 +5556,12 @@ "npm": ">=3" } }, - "node_modules/number-to-bn/node_modules/bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==", - "dev": true - }, - "node_modules/object-inspect": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, + "node_modules/number-to-bn/node_modules/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==", + "dev": true + }, "node_modules/obliterator": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.5.tgz", @@ -6557,23 +5609,6 @@ "node": ">=0.10.0" } }, - "node_modules/own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/p-cancelable": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", @@ -6653,9 +5688,9 @@ "dev": true }, "node_modules/package-json/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -6813,15 +5848,6 @@ "node": ">=4" } }, - "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -6832,9 +5858,9 @@ } }, "node_modules/prettier": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", - "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz", + "integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -6875,9 +5901,9 @@ } }, "node_modules/prettier-plugin-solidity/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -7011,9 +6037,9 @@ } }, "node_modules/readdirp": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.1.tgz", - "integrity": "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, "engines": { "node": ">= 14.18.0" @@ -7078,52 +6104,10 @@ "node": ">=6" } }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", - "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/registry-auth-token": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.3.tgz", - "integrity": "sha512-1bpc9IyC+e+CNFRaWyn77tk4xGG4PPUyfakSmA6F6cvUDjrm58dfyJ3II+9yb10EDkHoy1LaPSmHaWLOH3m6HA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.0.tgz", + "integrity": "sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==", "dev": true, "dependencies": { "@pnpm/npm-conf": "^2.1.0" @@ -7271,25 +6255,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -7310,39 +6275,6 @@ } ] }, - "node_modules/safe-push-apply": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", - "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -7553,52 +6485,6 @@ "randombytes": "^2.1.0" } }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", - "dev": true, - "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -7718,78 +6604,6 @@ "node": "*" } }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -7962,9 +6776,9 @@ } }, "node_modules/solhint/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -7974,18 +6788,9 @@ } }, "node_modules/solidity-ast": { - "version": "0.4.55", - "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.55.tgz", - "integrity": "sha512-qeEU/r/K+V5lrAw8iswf2/yfWAnSGs3WKPHI+zAFKFjX0dIBVXEU/swQ8eJQYHf6PJWUZFO2uWV4V1wEOkeQbA==", - "dev": true, - "dependencies": { - "array.prototype.findlast": "^1.2.2" - } - }, - "node_modules/solidity-comments-extractor": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/solidity-comments-extractor/-/solidity-comments-extractor-0.0.8.tgz", - "integrity": "sha512-htM7Vn6LhHreR+EglVMd2s+sZhcXAirB1Zlyrv5zBuTxieCvjfnRpd7iZk75m/u6NOlEyQ94C6TWbBn2cY7w8g==", + "version": "0.4.59", + "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.59.tgz", + "integrity": "sha512-I+CX0wrYUN9jDfYtcgWSe+OAowaXy8/1YQy7NS4ni5IBDmIYBq7ZzaP/7QqouLjzZapmQtvGLqCaYgoUWqBo5g==", "dev": true }, "node_modules/solidity-coverage": { @@ -8047,50 +6852,6 @@ "node": ">=4" } }, - "node_modules/solidity-coverage/node_modules/chalk/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/solidity-coverage/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/solidity-coverage/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, "node_modules/solidity-coverage/node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -8106,127 +6867,51 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, - "node_modules/solidity-coverage/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/solidity-coverage/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/solidity-coverage/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/solidity-coverage/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/solidity-coverage/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, "engines": { - "node": ">=10" + "node": ">=0.8.0" } }, - "node_modules/solidity-coverage/node_modules/mocha": { - "version": "10.8.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", - "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "node_modules/solidity-coverage/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "dependencies": { - "ansi-colors": "^4.1.3", - "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", - "debug": "^4.3.5", - "diff": "^5.2.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^8.1.0", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", - "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" }, "engines": { - "node": ">= 14.0.0" + "node": ">=6 <7 || >=8" } }, - "node_modules/solidity-coverage/node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/solidity-coverage/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": ">=4" } }, - "node_modules/solidity-coverage/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "node_modules/solidity-coverage/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, "node_modules/solidity-coverage/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -8247,15 +6932,6 @@ "node": ">=4" } }, - "node_modules/solidity-coverage/node_modules/supports-color/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/solidity-coverage/node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -8265,33 +6941,6 @@ "node": ">= 4.0.0" } }, - "node_modules/solidity-coverage/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/solidity-coverage/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/source-map": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", @@ -8331,9 +6980,9 @@ "dev": true }, "node_modules/stacktrace-parser": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", - "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz", + "integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==", "dev": true, "dependencies": { "type-fest": "^0.7.1" @@ -8404,62 +7053,6 @@ "node": ">=8" } }, - "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -8609,16 +7202,19 @@ "dev": true }, "node_modules/tinyglobby": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", - "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==", + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", + "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", "dev": true, "dependencies": { - "fdir": "^6.4.2", + "fdir": "^6.4.3", "picomatch": "^4.0.2" }, "engines": { "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, "node_modules/tinyglobby/node_modules/fdir": { @@ -8962,80 +7558,6 @@ "node": ">= 4.0.0" } }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/typescript": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", @@ -9071,24 +7593,6 @@ "node": ">=0.8.0" } }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/undici": { "version": "5.28.5", "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.5.tgz", @@ -9102,9 +7606,9 @@ } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "dev": true }, "node_modules/universalify": { @@ -9336,90 +7840,6 @@ "node": ">= 8" } }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "dev": true, - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", - "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "dev": true, - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", - "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/widest-line": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", @@ -9547,30 +7967,30 @@ } }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "dependencies": { - "cliui": "^8.0.1", + "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.3", + "string-width": "^4.2.0", "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "yargs-parser": "^20.2.2" }, "engines": { - "node": ">=12" + "node": ">=10" } }, "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, "engines": { - "node": ">=12" + "node": ">=10" } }, "node_modules/yargs-unparser": { diff --git a/package.json b/package.json index c7c0a1a4..9e02be7d 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "@solarity/solidity-lib", - "version": "2.7.17", + "version": "3.0.0-rc.0", "license": "MIT", "author": "Distributed Lab", "readme": "README.md", - "description": "Solidity Library for Savvies", + "description": "Solarity Solidity Library", "repository": { "type": "git", "url": "https://github.com/dl-solarity/solidity-lib" @@ -13,6 +13,7 @@ "solidity", "ethereum", "smart-contracts", + "zkp", "distributedlab", "solarity" ], @@ -26,16 +27,17 @@ "coverage": "npx hardhat coverage --solcoverjs ./.solcover.ts", "test": "npx hardhat test", "private-network": "npx hardhat node", - "lint-fix": "npm run lint-sol-fix && npm run lint-ts-fix && npm run lint-json-fix", + "lint-fix": "npm run lint-sol-fix && npm run lint-ts-fix && npm run lint-json-fix && npm run solhint-check", "lint-json-fix": "prettier --write \"./**/*.json\"", "lint-ts-fix": "prettier --write \"./**/*.ts\"", "lint-sol-fix": "prettier --write \"contracts/**/*.sol\"", + "solhint-check": "solhint \"./contracts/**/*.sol\"", "generate-docs": "npx hardhat markup", "publish-to-npm": "npm run lint-fix && bash ./scripts/publish.sh --public" }, "dependencies": { - "@openzeppelin/contracts": "4.9.6", - "@openzeppelin/contracts-upgradeable": "4.9.6", + "@openzeppelin/contracts": "5.2.0", + "@openzeppelin/contracts-upgradeable": "5.2.0", "@uniswap/v2-core": "1.0.1", "@uniswap/v2-periphery": "1.1.0-beta.0", "@uniswap/v3-core": "1.0.1", @@ -47,32 +49,31 @@ "@nomicfoundation/hardhat-chai-matchers": "^2.0.8", "@nomicfoundation/hardhat-ethers": "^3.0.8", "@nomicfoundation/hardhat-network-helpers": "^1.0.12", - "@solarity/hardhat-markup": "^1.0.8", + "@solarity/hardhat-markup": "^1.0.9", "@typechain/ethers-v6": "^0.5.1", "@typechain/hardhat": "^9.1.0", - "@types/chai": "^4.3.16", + "@types/chai": "^4.3.20", "@types/mocha": "^10.0.10", - "@types/node": "^18.16.0", + "@types/node": "^22.13.5", "bignumber.js": "^9.1.2", "chai": "^4.5.0", - "ethers": "^6.13.4", "circomlibjs": "^0.1.7", "dotenv": "^16.4.7", - "hardhat": "^2.22.0", + "ethers": "^6.13.5", + "hardhat": "^2.22.18", "hardhat-contract-sizer": "^2.10.0", "hardhat-gas-reporter": "^2.2.2", "husky": "^9.1.7", - "merkletreejs": "^0.4.0", - "mocha": "^11.0.1", + "merkletreejs": "^0.4.1", "mock-local-storage": "^1.1.24", - "prettier": "^3.4.2", - "prettier-plugin-solidity": "^1.4.1", - "solhint": "^5.0.3", + "prettier": "^3.5.2", + "prettier-plugin-solidity": "^1.4.2", + "solhint": "^5.0.5", "solhint-plugin-prettier": "^0.1.0", "solidity-coverage": "^0.8.14", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", "typechain": "^8.3.2", - "typescript": "^5.7.0" + "typescript": "^5.7.3" } } diff --git a/scripts/utils/constants.ts b/scripts/utils/constants.ts index 1a3e8e2e..ef8f9077 100644 --- a/scripts/utils/constants.ts +++ b/scripts/utils/constants.ts @@ -1,7 +1,3 @@ -import { ethers } from "hardhat"; - -export const ZERO_ADDR = "0x0000000000000000000000000000000000000000"; -export const ZERO_BYTES32 = "0x0000000000000000000000000000000000000000000000000000000000000000"; export const ETHER_ADDR = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; export const SECONDS_IN_DAY = 86400; @@ -10,5 +6,3 @@ export const SECONDS_IN_MONTH = SECONDS_IN_DAY * 30; export const DECIMAL = 10n ** 18n; export const PRECISION = 10n ** 25n; export const PERCENTAGE_100 = PRECISION * 100n; - -export const MAX_UINT256 = ethers.MaxUint256; diff --git a/test/access/MerkleWhitelisted.test.ts b/test/access/MerkleWhitelisted.test.ts index e394ae0c..c7e8a698 100644 --- a/test/access/MerkleWhitelisted.test.ts +++ b/test/access/MerkleWhitelisted.test.ts @@ -1,9 +1,10 @@ import { ethers } from "hardhat"; +import { expect } from "chai"; + import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { MerkleTree } from "merkletreejs"; -import { expect } from "chai"; + import { Reverter } from "@/test/helpers/reverter"; -import { ZERO_BYTES32 } from "@/scripts/utils/constants"; import { getRoot, getProof, buildTree } from "../helpers/merkle-tree-helper"; import { MerkleWhitelistedMock } from "@ethers-v6"; @@ -53,7 +54,7 @@ describe("MerkleWhitelisted", () => { }); it("should be zero if root is not set yet", async () => { - expect(await merkle.getMerkleRoot()).to.equal(ZERO_BYTES32); + expect(await merkle.getMerkleRoot()).to.equal(ethers.ZeroHash); }); it("should change merkle tree root properly", async () => { @@ -82,21 +83,27 @@ describe("MerkleWhitelisted", () => { }); it("should revert if the user is incorrect", async () => { - await expect( - merkle.connect(users[1]).onlyWhitelistedMethod(amounts[0], getProof(tree, leaves[0])), - ).to.be.revertedWith("MerkleWhitelisted: not whitelisted"); + const data = ethers.solidityPacked(["uint256", "address"], [amounts[0], users[1].address]); + + await expect(merkle.connect(users[1]).onlyWhitelistedMethod(amounts[0], getProof(tree, leaves[0]))) + .to.be.revertedWithCustomError(merkle, "LeafNotWhitelisted") + .withArgs(data); }); it("should revert if the amount is incorrect", async () => { - await expect( - merkle.connect(users[0]).onlyWhitelistedMethod(amounts[1], getProof(tree, leaves[0])), - ).to.be.revertedWith("MerkleWhitelisted: not whitelisted"); + const data = ethers.solidityPacked(["uint256", "address"], [amounts[1], users[0].address]); + + await expect(merkle.connect(users[0]).onlyWhitelistedMethod(amounts[1], getProof(tree, leaves[0]))) + .to.be.revertedWithCustomError(merkle, "LeafNotWhitelisted") + .withArgs(data); }); it("should revert if the proof is incorrect", async () => { - await expect( - merkle.connect(users[0]).onlyWhitelistedMethod(amounts[0], getProof(tree, leaves[1])), - ).to.be.revertedWith("MerkleWhitelisted: not whitelisted"); + const data = ethers.solidityPacked(["uint256", "address"], [amounts[0], users[0].address]); + + await expect(merkle.connect(users[0]).onlyWhitelistedMethod(amounts[0], getProof(tree, leaves[1]))) + .to.be.revertedWithCustomError(merkle, "LeafNotWhitelisted") + .withArgs(data); }); it("should not revert if all conditions are met", async () => { @@ -117,15 +124,15 @@ describe("MerkleWhitelisted", () => { }); it("should revert if the user is incorrect", async () => { - await expect(merkle.onlyWhitelistedUserMethod(getProof(tree, leaves[0]))).to.be.revertedWith( - "MerkleWhitelisted: not whitelisted", - ); + await expect(merkle.onlyWhitelistedUserMethod(getProof(tree, leaves[0]))) + .to.be.revertedWithCustomError(merkle, "UserNotWhitelisted") + .withArgs(OWNER.address); }); it("should revert if the proof is incorrect", async () => { - await expect(merkle.connect(users[0]).onlyWhitelistedUserMethod(getProof(tree, leaves[1]))).to.be.revertedWith( - "MerkleWhitelisted: not whitelisted", - ); + await expect(merkle.connect(users[0]).onlyWhitelistedUserMethod(getProof(tree, leaves[1]))) + .to.be.revertedWithCustomError(merkle, "UserNotWhitelisted") + .withArgs(users[0].address); }); it("should not revert if all conditions are met", async () => { diff --git a/test/access/MultiOwnable.test.ts b/test/access/MultiOwnable.test.ts index d10d5a66..49b37fef 100644 --- a/test/access/MultiOwnable.test.ts +++ b/test/access/MultiOwnable.test.ts @@ -1,8 +1,9 @@ import { ethers } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { expect } from "chai"; + +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; + import { Reverter } from "@/test/helpers/reverter"; -import { ZERO_ADDR } from "@/scripts/utils/constants"; import { MultiOwnableMock } from "@ethers-v6"; @@ -30,25 +31,26 @@ describe("MultiOwnable", () => { describe("access", () => { it("should not initialize twice", async () => { - await expect(multiOwnable.mockInit()).to.be.revertedWith("Initializable: contract is not initializing"); - await expect(multiOwnable.__MultiOwnableMock_init()).to.be.revertedWith( - "Initializable: contract is already initialized", - ); + await expect(multiOwnable.mockInit()).to.be.revertedWithCustomError(multiOwnable, "NotInitializing").withArgs(); + await expect(multiOwnable.__MultiOwnableMock_init()) + .to.be.revertedWithCustomError(multiOwnable, "InvalidInitialization") + .withArgs(); }); it("only owner should call these functions", async () => { - await expect(multiOwnable.connect(SECOND).addOwners([THIRD.address])).to.be.revertedWith( - "MultiOwnable: caller is not the owner", - ); + await expect(multiOwnable.connect(SECOND).addOwners([THIRD.address])) + .to.be.revertedWithCustomError(multiOwnable, "UnauthorizedAccount") + .withArgs(SECOND); await multiOwnable.addOwners([THIRD.address]); - await expect(multiOwnable.connect(SECOND).removeOwners([THIRD.address])).to.be.revertedWith( - "MultiOwnable: caller is not the owner", - ); - await expect(multiOwnable.connect(SECOND).renounceOwnership()).to.be.revertedWith( - "MultiOwnable: caller is not the owner", - ); + await expect(multiOwnable.connect(SECOND).removeOwners([THIRD.address])) + .to.be.revertedWithCustomError(multiOwnable, "UnauthorizedAccount") + .withArgs(SECOND); + + await expect(multiOwnable.connect(SECOND).renounceOwnership()) + .to.be.revertedWithCustomError(multiOwnable, "UnauthorizedAccount") + .withArgs(SECOND); }); }); @@ -61,8 +63,9 @@ describe("MultiOwnable", () => { }); it("should not add null address", async () => { - await expect(multiOwnable.addOwners([ZERO_ADDR])).to.be.revertedWith( - "MultiOwnable: zero address can not be added", + await expect(multiOwnable.addOwners([ethers.ZeroAddress])).to.be.revertedWithCustomError( + multiOwnable, + "InvalidOwner", ); }); }); diff --git a/test/access/PermanentOwnable.test.ts b/test/access/PermanentOwnable.test.ts index 8e9be596..f4f75b15 100644 --- a/test/access/PermanentOwnable.test.ts +++ b/test/access/PermanentOwnable.test.ts @@ -1,8 +1,9 @@ import { ethers } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { expect } from "chai"; + +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; + import { Reverter } from "@/test/helpers/reverter"; -import { ZERO_ADDR } from "@/scripts/utils/constants"; import { PermanentOwnableMock } from "@ethers-v6"; @@ -33,16 +34,16 @@ describe("PermanentOwnable", () => { it("should reject zero address during the owner initialization", async () => { const permanentOwnableMock = await ethers.getContractFactory("PermanentOwnableMock"); - await expect(permanentOwnableMock.deploy(ZERO_ADDR)).to.be.revertedWith( - "PermanentOwnable: zero address can not be the owner", - ); + await expect(permanentOwnableMock.deploy(ethers.ZeroAddress)) + .to.be.revertedWithCustomError(permanentOwnable, "InvalidOwner") + .withArgs(); }); it("only owner should call this function", async () => { expect(await permanentOwnable.connect(OWNER).onlyOwnerMethod()).to.emit(permanentOwnable, "ValidOwner"); - await expect(permanentOwnable.connect(OTHER).onlyOwnerMethod()).to.be.revertedWith( - "PermanentOwnable: caller is not the owner", - ); + await expect(permanentOwnable.connect(OTHER).onlyOwnerMethod()) + .to.be.revertedWithCustomError(permanentOwnable, "UnauthorizedAccount") + .withArgs(OTHER); }); }); }); diff --git a/test/access/RBAC.test.ts b/test/access/RBAC.test.ts index eeff76c5..6e8f3396 100644 --- a/test/access/RBAC.test.ts +++ b/test/access/RBAC.test.ts @@ -1,6 +1,7 @@ import { ethers } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { expect } from "chai"; +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; + import { Reverter } from "@/test/helpers/reverter"; import { RBACMock } from "@ethers-v6"; @@ -28,7 +29,7 @@ describe("RBAC", () => { describe("access", () => { it("should not initialize twice", async () => { - await expect(rbac.mockInit()).to.be.revertedWith("Initializable: contract is not initializing"); + await expect(rbac.mockInit()).to.be.revertedWithCustomError(rbac, "NotInitializing").withArgs(); }); }); @@ -125,11 +126,11 @@ describe("RBAC", () => { describe("user roles", () => { describe("empty roles", () => { it("should not grant empty roles", async () => { - await expect(rbac.grantRoles(SECOND.address, [])).to.be.revertedWith("RBAC: empty roles"); + await expect(rbac.grantRoles(SECOND.address, [])).to.be.revertedWithCustomError(rbac, "EmptyRoles"); }); it("should not revoke empty roles", async () => { - await expect(rbac.revokeRoles(SECOND.address, [])).to.be.revertedWith("RBAC: empty roles"); + await expect(rbac.revokeRoles(SECOND.address, [])).to.be.revertedWithCustomError(rbac, "EmptyRoles"); }); }); @@ -340,22 +341,29 @@ describe("RBAC", () => { describe("access", () => { it("should not call these functions without permission", async () => { - await expect(rbac.connect(SECOND).grantRoles(OWNER.address, ["ROLE"])).to.be.revertedWith( - "RBAC: no CREATE permission for resource RBAC_RESOURCE", - ); - await expect(rbac.connect(SECOND).revokeRoles(OWNER.address, ["MASTER"])).to.be.revertedWith( - "RBAC: no DELETE permission for resource RBAC_RESOURCE", - ); + await expect(rbac.connect(SECOND).grantRoles(OWNER.address, ["ROLE"])) + .to.be.revertedWithCustomError(rbac, "NoPermissionForResource") + .withArgs(SECOND.address, "CREATE", "RBAC_RESOURCE"); + + await expect(rbac.connect(SECOND).revokeRoles(OWNER.address, ["MASTER"])) + .to.be.revertedWithCustomError(rbac, "NoPermissionForResource") + .withArgs(SECOND.address, "DELETE", "RBAC_RESOURCE"); + await expect( rbac .connect(SECOND) .addPermissionsToRole("ROLE", [{ resource: "resource", permissions: ["permission"] }], true), - ).to.be.revertedWith("RBAC: no CREATE permission for resource RBAC_RESOURCE"); + ) + .to.be.revertedWithCustomError(rbac, "NoPermissionForResource") + .withArgs(SECOND.address, "CREATE", "RBAC_RESOURCE"); + await expect( rbac .connect(SECOND) .removePermissionsFromRole("ROLE", [{ resource: "resource", permissions: ["permission"] }], false), - ).to.be.revertedWith("RBAC: no DELETE permission for resource RBAC_RESOURCE"); + ) + .to.be.revertedWithCustomError(rbac, "NoPermissionForResource") + .withArgs(SECOND.address, "DELETE", "RBAC_RESOURCE"); }); }); }); diff --git a/test/access/extensions/RBACGroupable.test.ts b/test/access/extensions/RBACGroupable.test.ts index 57d9cbed..0bb034b7 100644 --- a/test/access/extensions/RBACGroupable.test.ts +++ b/test/access/extensions/RBACGroupable.test.ts @@ -1,11 +1,12 @@ import { ethers } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { expect } from "chai"; +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; + import { Reverter } from "@/test/helpers/reverter"; import { RBACGroupableMock } from "@ethers-v6"; -describe("RBAC", () => { +describe("RBACGroupable", () => { const reverter = new Reverter(); let OWNER: SignerWithAddress; @@ -26,9 +27,9 @@ describe("RBAC", () => { afterEach(reverter.revert); - describe("__RBACGroupable_init", () => { + describe("__ARBACGroupable_init", () => { it("should not initialize twice", async () => { - await expect(rbac.mockInit()).to.be.revertedWith("Initializable: contract is not initializing"); + await expect(rbac.mockInit()).to.be.revertedWithCustomError(rbac, "NotInitializing").withArgs(); }); }); @@ -90,13 +91,15 @@ describe("RBAC", () => { describe("grantGroupRoles", () => { it("should revert if no permission", async () => { - await expect(rbac.connect(SECOND).grantGroupRoles(GROUP_ALL_ROLES, ALL_ROLES)).to.be.revertedWith( - "RBAC: no CREATE permission for resource RBAC_RESOURCE", - ); + await expect(rbac.connect(SECOND).grantGroupRoles(GROUP_ALL_ROLES, ALL_ROLES)) + .to.be.revertedWithCustomError(rbac, "NoPermissionForResource") + .withArgs(SECOND.address, "CREATE", "RBAC_RESOURCE"); }); it("should revert if no roles provided", async () => { - await expect(rbac.grantGroupRoles(GROUP_ALL_ROLES, [])).to.be.revertedWith("RBACGroupable: empty roles"); + await expect(rbac.grantGroupRoles(GROUP_ALL_ROLES, [])) + .to.be.revertedWithCustomError(rbac, "EmptyRoles") + .withArgs(); }); it("should grant group roles if all conditions are met", async () => { @@ -118,13 +121,15 @@ describe("RBAC", () => { describe("revokeGroupRoles", () => { it("should revert if no permission", async () => { - await expect(rbac.connect(SECOND).revokeGroupRoles(GROUP_ALL_ROLES, ROLES_01)).to.be.rejectedWith( - "RBAC: no DELETE permission for resource RBAC_RESOURCE", - ); + await expect(rbac.connect(SECOND).revokeGroupRoles(GROUP_ALL_ROLES, ROLES_01)) + .to.be.revertedWithCustomError(rbac, "NoPermissionForResource") + .withArgs(SECOND.address, "DELETE", "RBAC_RESOURCE"); }); it("should revert if no roles provided", async () => { - await expect(rbac.revokeGroupRoles(GROUP_ALL_ROLES, [])).to.be.revertedWith("RBACGroupable: empty roles"); + await expect(rbac.revokeGroupRoles(GROUP_ALL_ROLES, [])) + .to.be.revertedWithCustomError(rbac, "EmptyRoles") + .withArgs(); }); it("should revoke group roles if all conditions are met", async () => { @@ -138,13 +143,15 @@ describe("RBAC", () => { describe("addUserToGroups", () => { it("should revert if no permission", async () => { - await expect( - rbac.connect(SECOND).addUserToGroups(SECOND.address, [GROUP_ROLES01, GROUP_ROLES12]), - ).to.be.rejectedWith("RBAC: no CREATE permission for resource RBAC_RESOURCE"); + await expect(rbac.connect(SECOND).addUserToGroups(SECOND.address, [GROUP_ROLES01, GROUP_ROLES12])) + .to.be.revertedWithCustomError(rbac, "NoPermissionForResource") + .withArgs(SECOND.address, "CREATE", "RBAC_RESOURCE"); }); it("should revert if no groups provided", async () => { - await expect(rbac.addUserToGroups(SECOND.address, [])).to.be.revertedWith("RBACGroupable: empty groups"); + await expect(rbac.addUserToGroups(SECOND.address, [])) + .to.be.revertedWithCustomError(rbac, "EmptyGroups") + .withArgs(); }); it("should add the user to groups if all conditions are met", async () => { @@ -158,9 +165,9 @@ describe("RBAC", () => { describe("toggleDefaultGroup", () => { it("should revert if no permission", async () => { - await expect(rbac.connect(SECOND).toggleDefaultGroup()).to.be.rejectedWith( - "RBAC: no UPDATE permission for resource RBAC_RESOURCE", - ); + await expect(rbac.connect(SECOND).toggleDefaultGroup()) + .to.be.revertedWithCustomError(rbac, "NoPermissionForResource") + .withArgs(SECOND.address, "UPDATE", "RBAC_RESOURCE"); }); it("should add the user to the default group automatically", async () => { @@ -191,15 +198,15 @@ describe("RBAC", () => { describe("removeUserFromGroups", () => { it("should revert if no permission", async () => { - await expect(rbac.connect(SECOND).removeUserFromGroups(SECOND.address, [GROUP_ROLES01])).to.be.revertedWith( - "RBAC: no DELETE permission for resource RBAC_RESOURCE", - ); + await expect(rbac.connect(SECOND).removeUserFromGroups(SECOND.address, [GROUP_ROLES01])) + .to.be.revertedWithCustomError(rbac, "NoPermissionForResource") + .withArgs(SECOND.address, "DELETE", "RBAC_RESOURCE"); }); it("should revert if no groups provided", async () => { - await expect(rbac.removeUserFromGroups(SECOND.address, [])).to.be.revertedWith( - "RBACGroupable: empty groups", - ); + await expect(rbac.removeUserFromGroups(SECOND.address, [])) + .to.be.revertedWithCustomError(rbac, "EmptyGroups") + .withArgs(); }); it("should remove the user from groups if all conditions are met", async () => { diff --git a/test/contracts-registry/ContractsRegistry.test.ts b/test/contracts-registry/ContractsRegistry.test.ts index 46d65f04..58564d5c 100644 --- a/test/contracts-registry/ContractsRegistry.test.ts +++ b/test/contracts-registry/ContractsRegistry.test.ts @@ -1,8 +1,9 @@ import { ethers } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { expect } from "chai"; + +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; + import { Reverter } from "@/test/helpers/reverter"; -import { ZERO_ADDR } from "@/scripts/utils/constants"; import { ContractsRegistryMock, DependantMock, DependantUpgradeMock, ERC20Mock } from "@ethers-v6"; @@ -29,10 +30,13 @@ describe("ContractsRegistry", () => { describe("access", () => { it("should not initialize twice", async () => { - await expect(contractsRegistry.mockInit()).to.be.revertedWith("Initializable: contract is not initializing"); - await expect(contractsRegistry.__OwnableContractsRegistry_init()).to.be.revertedWith( - "Initializable: contract is already initialized", - ); + await expect(contractsRegistry.mockInit()) + .to.be.revertedWithCustomError(contractsRegistry, "NotInitializing") + .withArgs(); + + await expect(contractsRegistry.__OwnableContractsRegistry_init()) + .to.be.revertedWithCustomError(contractsRegistry, "InvalidInitialization") + .withArgs(); }); it("should get proxy upgrader", async () => { @@ -40,66 +44,66 @@ describe("ContractsRegistry", () => { }); it("only owner should call these functions", async () => { - await expect(contractsRegistry.connect(SECOND).injectDependencies("")).to.be.revertedWith( - "Ownable: caller is not the owner", - ); + await expect(contractsRegistry.connect(SECOND).injectDependencies("")) + .to.be.revertedWithCustomError(contractsRegistry, "OwnableUnauthorizedAccount") + .withArgs(SECOND); - await expect(contractsRegistry.connect(SECOND).injectDependenciesWithData("", "0x")).to.be.revertedWith( - "Ownable: caller is not the owner", - ); + await expect(contractsRegistry.connect(SECOND).injectDependenciesWithData("", "0x")) + .to.be.revertedWithCustomError(contractsRegistry, "OwnableUnauthorizedAccount") + .withArgs(SECOND); - await expect(contractsRegistry.connect(SECOND).upgradeContract("", ZERO_ADDR)).to.be.revertedWith( - "Ownable: caller is not the owner", - ); + await expect(contractsRegistry.connect(SECOND).upgradeContract("", ethers.ZeroAddress)) + .to.be.revertedWithCustomError(contractsRegistry, "OwnableUnauthorizedAccount") + .withArgs(SECOND); - await expect(contractsRegistry.connect(SECOND).upgradeContractAndCall("", ZERO_ADDR, "0x")).to.be.revertedWith( - "Ownable: caller is not the owner", - ); + await expect(contractsRegistry.connect(SECOND).upgradeContractAndCall("", ethers.ZeroAddress, "0x")) + .to.be.revertedWithCustomError(contractsRegistry, "OwnableUnauthorizedAccount") + .withArgs(SECOND); - await expect(contractsRegistry.connect(SECOND).addContract("", ZERO_ADDR)).to.be.revertedWith( - "Ownable: caller is not the owner", - ); + await expect(contractsRegistry.connect(SECOND).addContract("", ethers.ZeroAddress)) + .to.be.revertedWithCustomError(contractsRegistry, "OwnableUnauthorizedAccount") + .withArgs(SECOND); - await expect(contractsRegistry.connect(SECOND).addProxyContract("", ZERO_ADDR)).to.be.revertedWith( - "Ownable: caller is not the owner", - ); + await expect(contractsRegistry.connect(SECOND).addProxyContract("", ethers.ZeroAddress)) + .to.be.revertedWithCustomError(contractsRegistry, "OwnableUnauthorizedAccount") + .withArgs(SECOND); - await expect(contractsRegistry.connect(SECOND).addProxyContractAndCall("", ZERO_ADDR, "0x")).to.be.revertedWith( - "Ownable: caller is not the owner", - ); + await expect(contractsRegistry.connect(SECOND).addProxyContractAndCall("", ethers.ZeroAddress, "0x")) + .to.be.revertedWithCustomError(contractsRegistry, "OwnableUnauthorizedAccount") + .withArgs(SECOND); - await expect(contractsRegistry.connect(SECOND).justAddProxyContract("", ZERO_ADDR)).to.be.revertedWith( - "Ownable: caller is not the owner", - ); + await expect(contractsRegistry.connect(SECOND).justAddProxyContract("", ethers.ZeroAddress)) + .to.be.revertedWithCustomError(contractsRegistry, "OwnableUnauthorizedAccount") + .withArgs(SECOND); - await expect(contractsRegistry.connect(SECOND).removeContract("")).to.be.revertedWith( - "Ownable: caller is not the owner", - ); + await expect(contractsRegistry.connect(SECOND).removeContract("")) + .to.be.revertedWithCustomError(contractsRegistry, "OwnableUnauthorizedAccount") + .withArgs(SECOND); }); }); describe("contract management", async () => { - it("should fail adding ZERO_ADDR address", async () => { - await expect( - contractsRegistry.addContract(await contractsRegistry.DEPENDANT_NAME(), ZERO_ADDR), - ).to.be.revertedWith("ContractsRegistry: zero address is forbidden"); - - await expect( - contractsRegistry.addProxyContract(await contractsRegistry.DEPENDANT_NAME(), ZERO_ADDR), - ).to.be.revertedWith("ContractsRegistry: zero address is forbidden"); - - await expect( - contractsRegistry.justAddProxyContract(await contractsRegistry.DEPENDANT_NAME(), ZERO_ADDR), - ).to.be.revertedWith("ContractsRegistry: zero address is forbidden"); + it("should fail adding ethers.ZeroAddress address", async () => { + await expect(contractsRegistry.addContract(await contractsRegistry.DEPENDANT_NAME(), ethers.ZeroAddress)) + .to.be.revertedWithCustomError(contractsRegistry, "ZeroAddressProvided") + .withArgs(await contractsRegistry.DEPENDANT_NAME()); + + await expect(contractsRegistry.addProxyContract(await contractsRegistry.DEPENDANT_NAME(), ethers.ZeroAddress)) + .to.be.revertedWithCustomError(contractsRegistry, "ZeroAddressProvided") + .withArgs(await contractsRegistry.DEPENDANT_NAME()); + + await expect(contractsRegistry.justAddProxyContract(await contractsRegistry.DEPENDANT_NAME(), ethers.ZeroAddress)) + .to.be.revertedWithCustomError(contractsRegistry, "ZeroAddressProvided") + .withArgs(await contractsRegistry.DEPENDANT_NAME()); }); it("should add and remove the contract", async () => { const DependantMock = await ethers.getContractFactory("DependantMock"); const dependant = await DependantMock.deploy(); - await expect(contractsRegistry.removeContract(await contractsRegistry.DEPENDANT_NAME())).to.be.revertedWith( - "ContractsRegistry: this mapping doesn't exist", - ); + await expect(contractsRegistry.removeContract(await contractsRegistry.DEPENDANT_NAME())) + .to.be.revertedWithCustomError(contractsRegistry, "NoMappingExists") + .withArgs(await contractsRegistry.DEPENDANT_NAME()); await contractsRegistry.addContract(await contractsRegistry.DEPENDANT_NAME(), await dependant.getAddress()); @@ -108,9 +112,10 @@ describe("ContractsRegistry", () => { await contractsRegistry.removeContract(await contractsRegistry.DEPENDANT_NAME()); - await expect(contractsRegistry.getDependantContract()).to.be.revertedWith( - "ContractsRegistry: this mapping doesn't exist", - ); + await expect(contractsRegistry.getDependantContract()) + .to.be.revertedWithCustomError(contractsRegistry, "NoMappingExists") + .withArgs(await contractsRegistry.DEPENDANT_NAME()); + expect(await contractsRegistry.hasContract(await contractsRegistry.DEPENDANT_NAME())).to.be.false; }); @@ -118,9 +123,9 @@ describe("ContractsRegistry", () => { const DependantMock = await ethers.getContractFactory("DependantMock"); const _dependant = await DependantMock.deploy(); - await expect(contractsRegistry.getImplementation(await contractsRegistry.DEPENDANT_NAME())).to.be.revertedWith( - "ContractsRegistry: this mapping doesn't exist", - ); + await expect(contractsRegistry.getImplementation(await contractsRegistry.DEPENDANT_NAME())) + .to.be.revertedWithCustomError(contractsRegistry, "NoMappingExists") + .withArgs(await contractsRegistry.DEPENDANT_NAME()); await contractsRegistry.addProxyContract(await contractsRegistry.DEPENDANT_NAME(), await _dependant.getAddress()); @@ -186,16 +191,18 @@ describe("ContractsRegistry", () => { await contractsRegistry.addContract(await contractsRegistry.DEPENDANT_NAME(), await dependant.getAddress()); - await expect(contractsRegistry.getImplementation(await contractsRegistry.DEPENDANT_NAME())).to.be.revertedWith( - "ContractsRegistry: not a proxy contract", - ); + await expect(contractsRegistry.getImplementation(await contractsRegistry.DEPENDANT_NAME())) + .to.be.revertedWithCustomError(contractsRegistry, "NotAProxy") + .withArgs(await contractsRegistry.DEPENDANT_NAME(), await dependant.getAddress()); await expect( contractsRegistry.upgradeContract( await contractsRegistry.DEPENDANT_NAME(), await _dependantUpgrade.getAddress(), ), - ).to.be.revertedWith("ContractsRegistry: not a proxy contract"); + ) + .to.be.revertedWithCustomError(contractsRegistry, "NotAProxy") + .withArgs(await contractsRegistry.DEPENDANT_NAME(), await dependant.getAddress()); }); it("should upgrade the contract", async () => { @@ -218,9 +225,9 @@ describe("ContractsRegistry", () => { }); it("should not upgrade non existing contract", async () => { - await expect( - contractsRegistry.upgradeContract("RANDOM CONTRACT", await _dependantUpgrade.getAddress()), - ).to.be.revertedWith("ContractsRegistry: this mapping doesn't exist"); + await expect(contractsRegistry.upgradeContract("RANDOM CONTRACT", await _dependantUpgrade.getAddress())) + .to.be.revertedWithCustomError(contractsRegistry, "NoMappingExists") + .withArgs("RANDOM CONTRACT"); }); it("should upgrade and call the contract", async () => { @@ -258,7 +265,7 @@ describe("ContractsRegistry", () => { }); it("should inject dependencies", async () => { - expect(await dependant.token()).to.equal(ZERO_ADDR); + expect(await dependant.token()).to.equal(ethers.ZeroAddress); await contractsRegistry.injectDependencies(await contractsRegistry.DEPENDANT_NAME()); @@ -266,7 +273,7 @@ describe("ContractsRegistry", () => { }); it("should inject dependencies with data", async () => { - expect(await dependant.token()).to.equal(ZERO_ADDR); + expect(await dependant.token()).to.equal(ethers.ZeroAddress); await contractsRegistry.injectDependenciesWithData(await contractsRegistry.DEPENDANT_NAME(), "0x112233"); @@ -274,9 +281,9 @@ describe("ContractsRegistry", () => { }); it("should not inject dependencies", async () => { - await expect(contractsRegistry.injectDependencies("RANDOM CONTRACT")).to.be.revertedWith( - "ContractsRegistry: this mapping doesn't exist", - ); + await expect(contractsRegistry.injectDependencies("RANDOM CONTRACT")) + .to.be.revertedWithCustomError(contractsRegistry, "NoMappingExists") + .withArgs("RANDOM CONTRACT"); }); it("should not allow random users to inject dependencies", async () => { @@ -284,15 +291,17 @@ describe("ContractsRegistry", () => { expect(await dependant.getInjector()).to.equal(await contractsRegistry.getAddress()); - await expect(dependant.setDependencies(await contractsRegistry.getAddress(), "0x")).to.be.revertedWith( - "Dependant: not an injector", - ); + await expect(dependant.setDependencies(await contractsRegistry.getAddress(), "0x")) + .to.be.revertedWithCustomError(dependant, "NotAnInjector") + .withArgs(await contractsRegistry.getAddress(), OWNER.address); }); it("should not allow random users to set new injector", async () => { await contractsRegistry.injectDependencies(await contractsRegistry.DEPENDANT_NAME()); - await expect(dependant.setInjector(OWNER)).to.be.revertedWith("Dependant: not an injector"); + await expect(dependant.setInjector(OWNER)) + .to.be.revertedWithCustomError(dependant, "NotAnInjector") + .withArgs(await dependant.getInjector(), OWNER.address); }); }); }); diff --git a/test/contracts-registry/pools/PoolContractsRegistry.test.ts b/test/contracts-registry/pools/PoolContractsRegistry.test.ts index 6d012e2b..dd6942d7 100644 --- a/test/contracts-registry/pools/PoolContractsRegistry.test.ts +++ b/test/contracts-registry/pools/PoolContractsRegistry.test.ts @@ -1,8 +1,9 @@ import { ethers } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { expect } from "chai"; + +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; + import { Reverter } from "@/test/helpers/reverter"; -import { ZERO_ADDR } from "@/scripts/utils/constants"; import { PoolContractsRegistryMock, ContractsRegistryPoolMock, PoolMock, PoolUpgradeMock, ERC20Mock } from "@ethers-v6"; @@ -46,7 +47,7 @@ describe("PoolContractsRegistry", () => { PoolContractsRegistry.attach(await contractsRegistry.getPoolContractsRegistryContract()) ); - await poolContractsRegistry.__OwnablePoolContractsRegistry_init(); + await poolContractsRegistry.__AOwnablePoolContractsRegistry_init(); await contractsRegistry.injectDependencies(await contractsRegistry.POOL_CONTRACTS_REGISTRY_NAME()); @@ -60,31 +61,35 @@ describe("PoolContractsRegistry", () => { describe("access", () => { it("should not initialize twice", async () => { - await expect(poolContractsRegistry.__OwnablePoolContractsRegistry_init()).to.be.revertedWith( - "Initializable: contract is already initialized", - ); + await expect(poolContractsRegistry.__AOwnablePoolContractsRegistry_init()) + .to.be.revertedWithCustomError(poolContractsRegistry, "InvalidInitialization") + .withArgs(); - await expect(poolContractsRegistry.mockInit()).to.be.revertedWith("Initializable: contract is not initializing"); + await expect(poolContractsRegistry.mockInit()) + .to.be.revertedWithCustomError(poolContractsRegistry, "NotInitializing") + .withArgs(); }); it("should not set dependencies from non dependant", async () => { - await expect(poolContractsRegistry.setDependencies(OWNER.address, "0x")).to.be.rejectedWith( - "Dependant: not an injector", - ); + const injector = await poolContractsRegistry.getInjector(); + + await expect(poolContractsRegistry.setDependencies(OWNER.address, "0x")) + .to.be.revertedWithCustomError(poolContractsRegistry, "NotAnInjector") + .withArgs(injector, OWNER.address); }); it("only owner should call these functions", async () => { - await expect(poolContractsRegistry.connect(SECOND).setNewImplementations([], [])).to.be.revertedWith( - "Ownable: caller is not the owner", - ); + await expect(poolContractsRegistry.connect(SECOND).setNewImplementations([], [])) + .to.be.revertedWithCustomError(contractsRegistry, "OwnableUnauthorizedAccount") + .withArgs(SECOND); - await expect( - poolContractsRegistry.connect(SECOND).injectDependenciesToExistingPools("", 0, 0), - ).to.be.revertedWith("Ownable: caller is not the owner"); + await expect(poolContractsRegistry.connect(SECOND).injectDependenciesToExistingPools("", 0, 0)) + .to.be.revertedWithCustomError(contractsRegistry, "OwnableUnauthorizedAccount") + .withArgs(SECOND); - await expect( - poolContractsRegistry.connect(SECOND).injectDependenciesToExistingPoolsWithData("", "0x", 0, 0), - ).to.be.revertedWith("Ownable: caller is not the owner"); + await expect(poolContractsRegistry.connect(SECOND).injectDependenciesToExistingPoolsWithData("", "0x", 0, 0)) + .to.be.revertedWithCustomError(contractsRegistry, "OwnableUnauthorizedAccount") + .withArgs(SECOND); }); }); @@ -93,16 +98,17 @@ describe("PoolContractsRegistry", () => { await poolContractsRegistry.setNewImplementations([NAME_1], [await token.getAddress()]); expect(await poolContractsRegistry.getImplementation(NAME_1)).to.equal(await token.getAddress()); - expect(await poolContractsRegistry.getProxyBeacon(NAME_1)).not.to.equal(ZERO_ADDR); + expect(await poolContractsRegistry.getProxyBeacon(NAME_1)).not.to.equal(ethers.ZeroAddress); }); it("should not get not existing implementation", async () => { - await expect(poolContractsRegistry.getImplementation(NAME_1)).to.be.revertedWith( - "PoolContractsRegistry: this mapping doesn't exist", - ); - await expect(poolContractsRegistry.getProxyBeacon(NAME_1)).to.be.revertedWith( - "PoolContractsRegistry: bad ProxyBeacon", - ); + await expect(poolContractsRegistry.getImplementation(NAME_1)) + .to.be.revertedWithCustomError(poolContractsRegistry, "NoMappingExists") + .withArgs(NAME_1); + + await expect(poolContractsRegistry.getProxyBeacon(NAME_1)) + .to.be.revertedWithCustomError(poolContractsRegistry, "ProxyDoesNotExist") + .withArgs(NAME_1); }); }); @@ -128,9 +134,9 @@ describe("PoolContractsRegistry", () => { }); it("only owner should be able to add pools", async () => { - await expect(poolContractsRegistry.connect(POOL_1).addProxyPool(NAME_1, POOL_1.address)).to.be.revertedWith( - "PoolContractsRegistry: not a factory", - ); + await expect(poolContractsRegistry.connect(POOL_1).addProxyPool(NAME_1, POOL_1.address)) + .to.be.revertedWithCustomError(poolContractsRegistry, "CallerNotAFactory") + .withArgs(POOL_1, OWNER.address); }); }); @@ -145,7 +151,7 @@ describe("PoolContractsRegistry", () => { it("should inject dependencies", async () => { await poolContractsRegistry.addProxyPool(NAME_1, await pool.getAddress()); - expect(await pool.token()).to.equal(ZERO_ADDR); + expect(await pool.token()).to.equal(ethers.ZeroAddress); await poolContractsRegistry.injectDependenciesToExistingPools(NAME_1, 0, 1); @@ -155,7 +161,7 @@ describe("PoolContractsRegistry", () => { it("should inject dependencies with data", async () => { await poolContractsRegistry.addProxyPool(NAME_1, await pool.getAddress()); - expect(await pool.token()).to.equal(ZERO_ADDR); + expect(await pool.token()).to.equal(ethers.ZeroAddress); await poolContractsRegistry.injectDependenciesToExistingPoolsWithData(NAME_1, "0x", 0, 1); @@ -163,9 +169,9 @@ describe("PoolContractsRegistry", () => { }); it("should not inject dependencies to 0 pools", async () => { - await expect(poolContractsRegistry.injectDependenciesToExistingPools(NAME_1, 0, 1)).to.be.revertedWith( - "PoolContractsRegistry: no pools to inject", - ); + await expect(poolContractsRegistry.injectDependenciesToExistingPools(NAME_1, 0, 1)) + .to.be.revertedWithCustomError(poolContractsRegistry, "NoPoolsToInject") + .withArgs(NAME_1); }); }); diff --git a/test/contracts-registry/pools/pool-factory/PoolFactory.test.ts b/test/contracts-registry/pools/pool-factory/PoolFactory.test.ts index 2d069df2..42ecf4c8 100644 --- a/test/contracts-registry/pools/pool-factory/PoolFactory.test.ts +++ b/test/contracts-registry/pools/pool-factory/PoolFactory.test.ts @@ -1,8 +1,9 @@ import { ethers } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { expect } from "chai"; + +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; + import { Reverter } from "@/test/helpers/reverter"; -import { ZERO_ADDR } from "@/scripts/utils/constants"; import { PoolFactoryMock, @@ -57,7 +58,7 @@ describe("PoolFactory", () => { ); poolFactory = PoolFactory.attach(await contractsRegistry.getPoolFactoryContract()); - await poolContractsRegistry.__OwnablePoolContractsRegistry_init(); + await poolContractsRegistry.__AOwnablePoolContractsRegistry_init(); await contractsRegistry.injectDependencies(await contractsRegistry.POOL_CONTRACTS_REGISTRY_NAME()); await contractsRegistry.injectDependencies(await contractsRegistry.POOL_FACTORY_NAME()); @@ -72,7 +73,9 @@ describe("PoolFactory", () => { describe("access", () => { it("should not set dependencies from non dependant", async () => { - await expect(poolFactory.setDependencies(OWNER, "0x")).to.be.revertedWith("Dependant: not an injector"); + await expect(poolFactory.setDependencies(OWNER, "0x")) + .to.be.revertedWithCustomError(poolFactory, "NotAnInjector") + .withArgs(await poolContractsRegistry.getInjector(), OWNER.address); }); }); @@ -100,14 +103,16 @@ describe("PoolFactory", () => { const beaconProxy = PublicBeaconProxy.attach(await pool.getAddress()); expect(await beaconProxy.implementation()).to.equal(await poolImpl.getAddress()); - expect(await pool.token()).not.to.equal(ZERO_ADDR); + expect(await pool.token()).not.to.equal(ethers.ZeroAddress); }); it("should not register pools", async () => { await contractsRegistry.addContract(await contractsRegistry.POOL_FACTORY_NAME(), OWNER); await contractsRegistry.injectDependencies(await contractsRegistry.POOL_CONTRACTS_REGISTRY_NAME()); - await expect(poolFactory.deployPool()).to.be.revertedWith("PoolContractsRegistry: not a factory"); + await expect(poolFactory.deployPool()) + .to.be.revertedWithCustomError(poolContractsRegistry, "CallerNotAFactory") + .withArgs(await poolFactory.getAddress(), OWNER.address); }); it("should deploy several pools", async () => { @@ -124,12 +129,13 @@ describe("PoolFactory", () => { const PoolMock = await ethers.getContractFactory("PoolMock"); const pool = PoolMock.attach((await poolContractsRegistry.listPools(NAME_1, 0, 1))[0]); - await expect(poolContractsRegistry.addProxyPool(NAME_1, await poolFactory.getAddress())).to.be.revertedWith( - "PoolContractsRegistry: not a factory", - ); - await expect(pool.setDependencies(await contractsRegistry.getAddress(), "0x")).to.be.revertedWith( - "Dependant: not an injector", - ); + await expect(poolContractsRegistry.addProxyPool(NAME_1, await poolFactory.getAddress())) + .to.be.revertedWithCustomError(poolContractsRegistry, "CallerNotAFactory") + .withArgs(OWNER.address, await poolFactory.getAddress()); + + await expect(pool.setDependencies(await contractsRegistry.getAddress(), "0x")) + .to.be.revertedWithCustomError(poolFactory, "NotAnInjector") + .withArgs(await pool.getInjector(), OWNER.address); }); it("should upgrade pools", async () => { diff --git a/test/contracts-registry/pools/presets/MultiOwnablePoolContractsRegistry.test.ts b/test/contracts-registry/pools/presets/MultiOwnablePoolContractsRegistry.test.ts index b571556d..49f6616d 100644 --- a/test/contracts-registry/pools/presets/MultiOwnablePoolContractsRegistry.test.ts +++ b/test/contracts-registry/pools/presets/MultiOwnablePoolContractsRegistry.test.ts @@ -1,6 +1,7 @@ import { ethers } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { expect } from "chai"; +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; + import { Reverter } from "@/test/helpers/reverter"; import { MultiOwnablePoolContractsRegistryMock } from "@ethers-v6"; @@ -20,7 +21,7 @@ describe("MultiOwnablePoolContractsRegistry", () => { ); poolContractsRegistry = await MultiOwnablePoolContractsRegistryMock.deploy(); - await poolContractsRegistry.__MultiOwnablePoolContractsRegistry_init(); + await poolContractsRegistry.__AMultiOwnablePoolContractsRegistry_init(); await reverter.snapshot(); }); @@ -29,23 +30,23 @@ describe("MultiOwnablePoolContractsRegistry", () => { describe("access", () => { it("should not initialize twice", async () => { - await expect(poolContractsRegistry.__MultiOwnablePoolContractsRegistry_init()).to.be.revertedWith( - "Initializable: contract is already initialized", - ); + await expect(poolContractsRegistry.__AMultiOwnablePoolContractsRegistry_init()) + .to.be.revertedWithCustomError(poolContractsRegistry, "InvalidInitialization") + .withArgs(); }); it("only owner should call these functions", async () => { - await expect(poolContractsRegistry.connect(SECOND).setNewImplementations([], [])).to.be.revertedWith( - "MultiOwnable: caller is not the owner", - ); + await expect(poolContractsRegistry.connect(SECOND).setNewImplementations([], [])) + .to.be.revertedWithCustomError(poolContractsRegistry, "UnauthorizedAccount") + .withArgs(SECOND.address); - await expect( - poolContractsRegistry.connect(SECOND).injectDependenciesToExistingPools("", 0, 0), - ).to.be.revertedWith("MultiOwnable: caller is not the owner"); + await expect(poolContractsRegistry.connect(SECOND).injectDependenciesToExistingPools("", 0, 0)) + .to.be.revertedWithCustomError(poolContractsRegistry, "UnauthorizedAccount") + .withArgs(SECOND.address); - await expect( - poolContractsRegistry.connect(SECOND).injectDependenciesToExistingPoolsWithData("", "0x", 0, 0), - ).to.be.revertedWith("MultiOwnable: caller is not the owner"); + await expect(poolContractsRegistry.connect(SECOND).injectDependenciesToExistingPoolsWithData("", "0x", 0, 0)) + .to.be.revertedWithCustomError(poolContractsRegistry, "UnauthorizedAccount") + .withArgs(SECOND.address); }); }); diff --git a/test/contracts-registry/presets/MultiOwnableContractsRegistry.test.ts b/test/contracts-registry/presets/MultiOwnableContractsRegistry.test.ts index 1ba2ff22..0133f914 100644 --- a/test/contracts-registry/presets/MultiOwnableContractsRegistry.test.ts +++ b/test/contracts-registry/presets/MultiOwnableContractsRegistry.test.ts @@ -1,8 +1,9 @@ import { ethers } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { expect } from "chai"; + +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; + import { Reverter } from "@/test/helpers/reverter"; -import { ZERO_ADDR } from "@/scripts/utils/constants"; import { MultiOwnableContractsRegistry } from "@ethers-v6"; @@ -28,47 +29,47 @@ describe("MultiOwnableContractsRegistry", () => { describe("access", () => { it("should not initialize twice", async () => { - await expect(contractsRegistry.__MultiOwnableContractsRegistry_init()).to.be.revertedWith( - "Initializable: contract is already initialized", - ); + await expect(contractsRegistry.__MultiOwnableContractsRegistry_init()) + .to.be.revertedWithCustomError(contractsRegistry, "InvalidInitialization") + .withArgs(); }); it("only owner should call these functions", async () => { - await expect(contractsRegistry.connect(SECOND).injectDependencies("")).to.be.revertedWith( - "MultiOwnable: caller is not the owner", - ); + await expect(contractsRegistry.connect(SECOND).injectDependencies("")) + .to.be.revertedWithCustomError(contractsRegistry, "UnauthorizedAccount") + .withArgs(SECOND.address); - await expect(contractsRegistry.connect(SECOND).injectDependenciesWithData("", "0x")).to.be.revertedWith( - "MultiOwnable: caller is not the owner", - ); + await expect(contractsRegistry.connect(SECOND).injectDependenciesWithData("", "0x")) + .to.be.revertedWithCustomError(contractsRegistry, "UnauthorizedAccount") + .withArgs(SECOND.address); - await expect(contractsRegistry.connect(SECOND).upgradeContract("", ZERO_ADDR)).to.be.revertedWith( - "MultiOwnable: caller is not the owner", - ); + await expect(contractsRegistry.connect(SECOND).upgradeContract("", ethers.ZeroAddress)) + .to.be.revertedWithCustomError(contractsRegistry, "UnauthorizedAccount") + .withArgs(SECOND.address); - await expect(contractsRegistry.connect(SECOND).upgradeContractAndCall("", ZERO_ADDR, "0x")).to.be.revertedWith( - "MultiOwnable: caller is not the owner", - ); + await expect(contractsRegistry.connect(SECOND).upgradeContractAndCall("", ethers.ZeroAddress, "0x")) + .to.be.revertedWithCustomError(contractsRegistry, "UnauthorizedAccount") + .withArgs(SECOND.address); - await expect(contractsRegistry.connect(SECOND).addContract("", ZERO_ADDR)).to.be.revertedWith( - "MultiOwnable: caller is not the owner", - ); + await expect(contractsRegistry.connect(SECOND).addContract("", ethers.ZeroAddress)) + .to.be.revertedWithCustomError(contractsRegistry, "UnauthorizedAccount") + .withArgs(SECOND.address); - await expect(contractsRegistry.connect(SECOND).addProxyContract("", ZERO_ADDR)).to.be.revertedWith( - "MultiOwnable: caller is not the owner", - ); + await expect(contractsRegistry.connect(SECOND).addProxyContract("", ethers.ZeroAddress)) + .to.be.revertedWithCustomError(contractsRegistry, "UnauthorizedAccount") + .withArgs(SECOND.address); - await expect(contractsRegistry.connect(SECOND).addProxyContractAndCall("", ZERO_ADDR, "0x")).to.be.revertedWith( - "MultiOwnable: caller is not the owner", - ); + await expect(contractsRegistry.connect(SECOND).addProxyContractAndCall("", ethers.ZeroAddress, "0x")) + .to.be.revertedWithCustomError(contractsRegistry, "UnauthorizedAccount") + .withArgs(SECOND.address); - await expect(contractsRegistry.connect(SECOND).justAddProxyContract("", ZERO_ADDR)).to.be.revertedWith( - "MultiOwnable: caller is not the owner", - ); + await expect(contractsRegistry.connect(SECOND).justAddProxyContract("", ethers.ZeroAddress)) + .to.be.revertedWithCustomError(contractsRegistry, "UnauthorizedAccount") + .withArgs(SECOND.address); - await expect(contractsRegistry.connect(SECOND).removeContract("")).to.be.revertedWith( - "MultiOwnable: caller is not the owner", - ); + await expect(contractsRegistry.connect(SECOND).removeContract("")) + .to.be.revertedWithCustomError(contractsRegistry, "UnauthorizedAccount") + .withArgs(SECOND.address); }); }); @@ -78,17 +79,17 @@ describe("MultiOwnableContractsRegistry", () => { await expect(contractsRegistry.injectDependenciesWithData("", "0x")).to.be.reverted; - await expect(contractsRegistry.upgradeContract("", ZERO_ADDR)).to.be.reverted; + await expect(contractsRegistry.upgradeContract("", ethers.ZeroAddress)).to.be.reverted; - await expect(contractsRegistry.upgradeContractAndCall("", ZERO_ADDR, "0x")).to.be.reverted; + await expect(contractsRegistry.upgradeContractAndCall("", ethers.ZeroAddress, "0x")).to.be.reverted; - await expect(contractsRegistry.addContract("", ZERO_ADDR)).to.be.reverted; + await expect(contractsRegistry.addContract("", ethers.ZeroAddress)).to.be.reverted; - await expect(contractsRegistry.addProxyContract("", ZERO_ADDR)).to.be.reverted; + await expect(contractsRegistry.addProxyContract("", ethers.ZeroAddress)).to.be.reverted; - await expect(contractsRegistry.addProxyContractAndCall("", ZERO_ADDR, "0x")).to.be.reverted; + await expect(contractsRegistry.addProxyContractAndCall("", ethers.ZeroAddress, "0x")).to.be.reverted; - await expect(contractsRegistry.justAddProxyContract("", ZERO_ADDR)).to.be.reverted; + await expect(contractsRegistry.justAddProxyContract("", ethers.ZeroAddress)).to.be.reverted; await expect(contractsRegistry.removeContract("")).to.be.reverted; }); diff --git a/test/diamond/Diamond.test.ts b/test/diamond/Diamond.test.ts index 230a2b49..aef9d779 100644 --- a/test/diamond/Diamond.test.ts +++ b/test/diamond/Diamond.test.ts @@ -1,9 +1,10 @@ import { ethers } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { expect } from "chai"; + +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; + import { Reverter } from "@/test/helpers/reverter"; import { getSelectors, FacetAction } from "@/test/helpers/diamond-helper"; -import { ZERO_ADDR, ZERO_BYTES32 } from "@/scripts/utils/constants"; import { wei } from "@/scripts/utils/utils"; import { OwnableDiamondMock, DummyFacetMock, DummyInitMock, Diamond } from "@ethers-v6"; @@ -31,15 +32,15 @@ describe("Diamond", () => { describe("access", () => { it("should initialize only once", async () => { - await expect(diamond.__OwnableDiamondMock_init()).to.be.revertedWith( - "Initializable: contract is already initialized", - ); + await expect(diamond.__OwnableDiamondMock_init()) + .to.be.revertedWithCustomError(diamond, "AlreadyInitialized") + .withArgs(); }); it("should initialize only by top level contract", async () => { - await expect(diamond.__OwnableDiamondDirect_init()).to.be.revertedWith( - "Initializable: contract is not initializing", - ); + await expect(diamond.__OwnableDiamondDirect_init()) + .to.be.revertedWithCustomError(diamond, "NotInitializing") + .withArgs(); }); }); @@ -57,21 +58,25 @@ describe("Diamond", () => { it("should renounce ownership", async () => { await diamond.renounceOwnership(); - expect(await diamond.owner()).to.equal(ZERO_ADDR); + expect(await diamond.owner()).to.equal(ethers.ZeroAddress); }); it("should not transfer ownership from non-owner", async () => { - await expect(diamond.connect(SECOND).transferOwnership(SECOND.address)).to.be.revertedWith( - "DiamondOwnable: not an owner", - ); + await expect(diamond.connect(SECOND).transferOwnership(SECOND.address)) + .to.be.revertedWithCustomError(diamond, "CallerNotOwner") + .withArgs(SECOND.address, OWNER.address); }); it("should not renounce ownership from non-owner", async () => { - await expect(diamond.connect(SECOND).renounceOwnership()).to.be.revertedWith("DiamondOwnable: not an owner"); + await expect(diamond.connect(SECOND).renounceOwnership()) + .to.be.revertedWithCustomError(diamond, "CallerNotOwner") + .withArgs(SECOND.address, OWNER.address); }); it("should not transfer ownership to zero address", async () => { - await expect(diamond.transferOwnership(ZERO_ADDR)).to.be.revertedWith("DiamondOwnable: zero address owner"); + await expect(diamond.transferOwnership(ethers.ZeroAddress)) + .to.be.revertedWithCustomError(diamond, "InvalidOwner") + .withArgs(); }); }); @@ -92,7 +97,7 @@ describe("Diamond", () => { expect(await diamond.facets()).to.deep.equal([]); expect(await diamond.facetFunctionSelectors(await dummyFacet.getAddress())).to.deep.equal([]); expect(await diamond.facetAddresses()).to.deep.equal([]); - expect(await diamond.facetAddress("0x11223344")).to.equal(ZERO_ADDR); + expect(await diamond.facetAddress("0x11223344")).to.equal(ethers.ZeroAddress); }); }); @@ -126,25 +131,18 @@ describe("Diamond", () => { expect(await dummyFacet.getDummyString()).to.be.equal("dummy facet initialized"); }); - it("should revert if init address is not contract", async () => { - const init = dummyInit.init.fragment.selector; - await expect(diamond.diamondCutLong(facets, SECOND, init)).to.be.revertedWith( - "Diamond: init_ address has no code", - ); - }); - it("should revert if init function reverted", async () => { const initWithError = dummyInit.initWithError.fragment.selector; - await expect(diamond.diamondCutLong(facets, await dummyInit.getAddress(), initWithError)).to.be.revertedWith( - "Diamond: initialization function reverted", - ); + await expect(diamond.diamondCutLong(facets, await dummyInit.getAddress(), initWithError)) + .to.be.revertedWithCustomError(diamond, "InitializationReverted") + .withArgs(await dummyInit.getAddress(), initWithError); }); it("should revert if init function reverted with message", async () => { const initWithErrorMsg = dummyInit.initWithErrorMsg.fragment.selector; - await expect(diamond.diamondCutLong(facets, await dummyInit.getAddress(), initWithErrorMsg)).to.be.revertedWith( - "DiamondInit: init error", - ); + await expect(diamond.diamondCutLong(facets, await dummyInit.getAddress(), initWithErrorMsg)) + .to.be.revertedWithCustomError(dummyInit, "InitError") + .withArgs(); }); }); @@ -162,43 +160,44 @@ describe("Diamond", () => { it("should add facet correctly", async () => { const tx = diamond.diamondCutShort(facets); - await expect(tx).to.emit(diamond, "DiamondCut").withArgs(facets.map(Object.values), ZERO_ADDR, "0x"); + await expect(tx).to.emit(diamond, "DiamondCut").withArgs(facets.map(Object.values), ethers.ZeroAddress, "0x"); expect(await diamond.facets()).to.deep.equal([[await dummyFacet.getAddress(), selectors]]); expect(await diamond.facetFunctionSelectors(await dummyFacet.getAddress())).to.deep.equal(selectors); expect(await diamond.facetAddresses()).to.deep.equal([await dummyFacet.getAddress()]); expect(await diamond.facetAddress(selectors[0])).to.equal(await dummyFacet.getAddress()); - expect(await diamond.facetAddress("0x11223344")).to.equal(ZERO_ADDR); + expect(await diamond.facetAddress("0x11223344")).to.equal(ethers.ZeroAddress); }); it("should not add facet with zero address", async () => { - facets[0].facetAddress = ZERO_ADDR; - await expect(diamond.diamondCutShort(facets)).to.be.revertedWith("Diamond: facet cannot be zero address"); - }); - - it("should not add non-contract as a facet", async () => { - facets[0].facetAddress = SECOND.address; - await expect(diamond.diamondCutShort(facets)).to.be.revertedWith("Diamond: facet is not a contract"); + facets[0].facetAddress = ethers.ZeroAddress; + await expect(diamond.diamondCutShort(facets)) + .to.be.revertedWithCustomError(diamond, "FacetIsZeroAddress") + .withArgs(); }); it("should not add facet when no selectors provided", async () => { facets[0].functionSelectors = []; - await expect(diamond.diamondCutShort(facets)).to.be.revertedWith("Diamond: no selectors provided"); + await expect(diamond.diamondCutShort(facets)) + .to.be.revertedWithCustomError(diamond, "NoSelectorsProvided") + .withArgs(); }); it("only owner should add facets", async () => { - await expect(diamond.connect(SECOND).diamondCutShort(facets)).to.be.revertedWith( - "DiamondOwnable: not an owner", - ); + await expect(diamond.connect(SECOND).diamondCutShort(facets)) + .to.be.revertedWithCustomError(diamond, "CallerNotOwner") + .withArgs(SECOND.address, OWNER.address); - await expect(diamond.connect(SECOND).diamondCutLong(facets, ZERO_ADDR, ZERO_BYTES32)).to.be.revertedWith( - "DiamondOwnable: not an owner", - ); + await expect(diamond.connect(SECOND).diamondCutLong(facets, ethers.ZeroAddress, ethers.ZeroHash)) + .to.be.revertedWithCustomError(diamond, "CallerNotOwner") + .withArgs(SECOND.address, OWNER.address); }); it("should not add duplicate selectors", async () => { await diamond.diamondCutShort(facets); - await expect(diamond.diamondCutShort(facets)).to.be.revertedWith("Diamond: selector already added"); + await expect(diamond.diamondCutShort(facets)) + .to.be.revertedWithCustomError(diamond, "SelectorAlreadyAdded") + .withArgs(facets[0].facetAddress, selectors[0]); }); }); @@ -222,13 +221,13 @@ describe("Diamond", () => { facets[0].functionSelectors = selectors.slice(1); const tx = diamond.diamondCutShort(facets); - await expect(tx).to.emit(diamond, "DiamondCut").withArgs(facets.map(Object.values), ZERO_ADDR, "0x"); + await expect(tx).to.emit(diamond, "DiamondCut").withArgs(facets.map(Object.values), ethers.ZeroAddress, "0x"); expect(await diamond.facets()).to.deep.equal([[await dummyFacet.getAddress(), [selectors[0]]]]); expect(await diamond.facetAddresses()).to.deep.equal([await dummyFacet.getAddress()]); expect(await diamond.facetFunctionSelectors(await dummyFacet.getAddress())).to.deep.equal([selectors[0]]); expect(await diamond.facetAddress(selectors[0])).to.equal(await dummyFacet.getAddress()); - expect(await diamond.facetAddress(selectors[1])).to.equal(ZERO_ADDR); + expect(await diamond.facetAddress(selectors[1])).to.equal(ethers.ZeroAddress); }); it("should fully remove facets", async () => { @@ -238,22 +237,26 @@ describe("Diamond", () => { facets[0].action = FacetAction.Remove; const tx = diamond.diamondCutShort(facets); - await expect(tx).to.emit(diamond, "DiamondCut").withArgs(facets.map(Object.values), ZERO_ADDR, "0x"); + await expect(tx).to.emit(diamond, "DiamondCut").withArgs(facets.map(Object.values), ethers.ZeroAddress, "0x"); expect(await diamond.facets()).to.deep.equal([]); expect(await diamond.facetAddresses()).to.deep.equal([]); expect(await diamond.facetFunctionSelectors(await dummyFacet.getAddress())).to.deep.equal([]); - expect(await diamond.facetAddress(selectors[0])).to.equal(ZERO_ADDR); + expect(await diamond.facetAddress(selectors[0])).to.equal(ethers.ZeroAddress); }); it("should not remove facet when facet is zero address", async () => { - facets[0].facetAddress = ZERO_ADDR; - await expect(diamond.diamondCutShort(facets)).to.be.revertedWith("Diamond: facet cannot be zero address"); + facets[0].facetAddress = ethers.ZeroAddress; + await expect(diamond.diamondCutShort(facets)) + .to.be.revertedWithCustomError(diamond, "FacetIsZeroAddress") + .withArgs(); }); it("should not remove facet when no selectors provided", async () => { facets[0].functionSelectors = []; - await expect(diamond.diamondCutShort(facets)).to.be.revertedWith("Diamond: no selectors provided"); + await expect(diamond.diamondCutShort(facets)) + .to.be.revertedWithCustomError(diamond, "NoSelectorsProvided") + .withArgs(); }); it("should not remove selectors from another facet", async () => { @@ -263,17 +266,19 @@ describe("Diamond", () => { facets[0].action = FacetAction.Remove; facets[0].facetAddress = await diamond.getAddress(); - await expect(diamond.diamondCutShort(facets)).to.be.revertedWith("Diamond: selector from another facet"); + await expect(diamond.diamondCutShort(facets)) + .to.be.revertedWithCustomError(diamond, "SelectorFromAnotherFacet") + .withArgs(facets[0].functionSelectors[0]); }); it("only owner should remove facets", async () => { - await expect(diamond.connect(SECOND).diamondCutShort(facets)).to.be.revertedWith( - "DiamondOwnable: not an owner", - ); + await expect(diamond.connect(SECOND).diamondCutShort(facets)) + .to.be.revertedWithCustomError(diamond, "CallerNotOwner") + .withArgs(SECOND.address, OWNER.address); - await expect(diamond.connect(SECOND).diamondCutLong(facets, ZERO_ADDR, ZERO_BYTES32)).to.be.revertedWith( - "DiamondOwnable: not an owner", - ); + await expect(diamond.connect(SECOND).diamondCutLong(facets, ethers.ZeroAddress, ethers.ZeroHash)) + .to.be.revertedWithCustomError(diamond, "CallerNotOwner") + .withArgs(SECOND.address, OWNER.address); }); }); @@ -301,7 +306,7 @@ describe("Diamond", () => { const tx = diamond.diamondCutShort(facets); - await expect(tx).to.emit(diamond, "DiamondCut").withArgs(facets.map(Object.values), ZERO_ADDR, "0x"); + await expect(tx).to.emit(diamond, "DiamondCut").withArgs(facets.map(Object.values), ethers.ZeroAddress, "0x"); expect(await diamond.facets()).to.deep.equal([ [await dummyFacet.getAddress(), [selectors[0]]], @@ -324,7 +329,7 @@ describe("Diamond", () => { const tx = diamond.diamondCutShort(facets); - await expect(tx).to.emit(diamond, "DiamondCut").withArgs(facets.map(Object.values), ZERO_ADDR, "0x"); + await expect(tx).to.emit(diamond, "DiamondCut").withArgs(facets.map(Object.values), ethers.ZeroAddress, "0x"); expect(await diamond.facets()).to.deep.equal([[await dummyFacet2.getAddress(), selectors]]); expect(await diamond.facetFunctionSelectors(await dummyFacet.getAddress())).to.deep.equal([]); @@ -333,18 +338,17 @@ describe("Diamond", () => { }); it("should not replace facet when facet is zero address", async () => { - facets[0].facetAddress = ZERO_ADDR; - await expect(diamond.diamondCutShort(facets)).to.be.revertedWith("Diamond: facet cannot be zero address"); - }); - - it("should not replace non-contract as a facet", async () => { - facets[0].facetAddress = SECOND.address; - await expect(diamond.diamondCutShort(facets)).to.be.revertedWith("Diamond: facet is not a contract"); + facets[0].facetAddress = ethers.ZeroAddress; + await expect(diamond.diamondCutShort(facets)) + .to.be.revertedWithCustomError(diamond, "FacetIsZeroAddress") + .withArgs(); }); it("should not replace facet when no selectors provided", async () => { facets[0].functionSelectors = []; - await expect(diamond.diamondCutShort(facets)).to.be.revertedWith("Diamond: no selectors provided"); + await expect(diamond.diamondCutShort(facets)) + .to.be.revertedWithCustomError(diamond, "NoSelectorsProvided") + .withArgs(); }); it("should not replace facet with the same facet", async () => { @@ -352,7 +356,9 @@ describe("Diamond", () => { await diamond.diamondCutShort(facets); facets[0].action = FacetAction.Replace; - await expect(diamond.diamondCutShort(facets)).to.be.revertedWith("Diamond: cannot replace to the same facet"); + await expect(diamond.diamondCutShort(facets)) + .to.be.revertedWithCustomError(diamond, "SelectorIsAlreadyInThisFaucet") + .withArgs(facets[0].functionSelectors[0], facets[0].facetAddress); }); it("should not replace facet if selector is not registered", async () => { @@ -362,17 +368,19 @@ describe("Diamond", () => { facets[0].action = FacetAction.Replace; // set random selector facets[0].functionSelectors = ["0x00000000"]; - await expect(diamond.diamondCutShort(facets)).to.be.revertedWith("Diamond: no facet found for selector"); + await expect(diamond.diamondCutShort(facets)) + .to.be.revertedWithCustomError(diamond, "NoFacetForSelector") + .withArgs(facets[0].functionSelectors[0]); }); it("only owner should replace facets", async () => { - await expect(diamond.connect(SECOND).diamondCutShort(facets)).to.be.revertedWith( - "DiamondOwnable: not an owner", - ); + await expect(diamond.connect(SECOND).diamondCutShort(facets)) + .to.be.revertedWithCustomError(diamond, "CallerNotOwner") + .withArgs(SECOND.address, OWNER.address); - await expect(diamond.connect(SECOND).diamondCutLong(facets, ZERO_ADDR, ZERO_BYTES32)).to.be.revertedWith( - "DiamondOwnable: not an owner", - ); + await expect(diamond.connect(SECOND).diamondCutLong(facets, ethers.ZeroAddress, ethers.ZeroHash)) + .to.be.revertedWithCustomError(diamond, "CallerNotOwner") + .withArgs(SECOND.address, OWNER.address); }); }); @@ -415,7 +423,9 @@ describe("Diamond", () => { const DummyFacetMock = await ethers.getContractFactory("DummyFacetMock"); const facet = DummyFacetMock.attach(await diamond.getAddress()); - await expect(facet.getDummyString()).to.be.revertedWith("Diamond: selector is not registered"); + await expect(facet.getDummyString()) + .to.be.revertedWithCustomError(diamond, "SelectorNotRegistered") + .withArgs(getSelectors(facet.interface)[1]); }); it("should not receive ether if receive is not added", async () => { @@ -424,7 +434,9 @@ describe("Diamond", () => { value: wei("1"), }; - await expect(OWNER.sendTransaction(tx)).to.be.revertedWith("Diamond: selector is not registered"); + await expect(OWNER.sendTransaction(tx)) + .to.be.revertedWithCustomError(diamond, "SelectorNotRegistered") + .withArgs("0x00000000"); }); }); }); diff --git a/test/diamond/DiamondAccessControl.ts b/test/diamond/DiamondAccessControl.ts index 2fd71f06..d2bb815e 100644 --- a/test/diamond/DiamondAccessControl.ts +++ b/test/diamond/DiamondAccessControl.ts @@ -1,16 +1,16 @@ import { ethers } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { expect } from "chai"; +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; + import { Reverter } from "@/test/helpers/reverter"; import { getSelectors, FacetAction } from "@/test/helpers/diamond-helper"; -import { ZERO_BYTES32 } from "@/scripts/utils/constants"; import { OwnableDiamondMock, Diamond, DiamondAccessControlMock } from "@ethers-v6"; describe("DiamondAccessControl", () => { const reverter = new Reverter(); - const ADMIN_ROLE = ZERO_BYTES32; + const ADMIN_ROLE = ethers.ZeroHash; const AGENT_ROLE = "0x0000000000000000000000000000000000000000000000000000000000000001"; let OWNER: SignerWithAddress; @@ -20,10 +20,6 @@ describe("DiamondAccessControl", () => { let access: DiamondAccessControlMock; let diamond: OwnableDiamondMock; - const hasRoleErrorMessage = async (account: SignerWithAddress, role: string) => { - return `AccessControl: account ${(await account.getAddress()).toLowerCase()} is missing role ${role}`; - }; - before("setup", async () => { [OWNER, SECOND, THIRD] = await ethers.getSigners(); @@ -56,30 +52,32 @@ describe("DiamondAccessControl", () => { describe("access", () => { it("should initialize only once", async () => { - await expect(access.__DiamondAccessControlMock_init()).to.be.revertedWith( - "Initializable: contract is already initialized", - ); + await expect(access.__DiamondAccessControlMock_init()) + .to.be.revertedWithCustomError(diamond, "AlreadyInitialized") + .withArgs(); }); it("should initialize only by top level contract", async () => { - await expect(access.__DiamondAccessControlDirect_init()).to.be.revertedWith( - "Initializable: contract is not initializing", - ); + await expect(access.__DiamondAccessControlDirect_init()) + .to.be.revertedWithCustomError(diamond, "NotInitializing") + .withArgs(); }); }); describe("DiamondAccessControl functions", () => { describe("grantRole", async () => { it("should not grant role if not admin", async () => { - await expect(access.connect(SECOND).grantRole(AGENT_ROLE, SECOND)).to.be.revertedWith( - await hasRoleErrorMessage(SECOND, ADMIN_ROLE), - ); + await expect(access.connect(SECOND).grantRole(AGENT_ROLE, SECOND)) + .to.be.revertedWithCustomError(access, "RoleNotGranted") + .withArgs(await access.getRoleAdmin(AGENT_ROLE), SECOND); }); it("should not grant role if it's granted", async () => { await access.grantRole(AGENT_ROLE, SECOND); - await expect(access.grantRole(AGENT_ROLE, SECOND)).to.be.revertedWith("AccessControl: role is granted"); + await expect(access.grantRole(AGENT_ROLE, SECOND)) + .to.be.revertedWithCustomError(access, "RoleAlreadyGranted") + .withArgs(AGENT_ROLE, SECOND); }); it("should grant role if all conditions are met", async () => { @@ -95,15 +93,17 @@ describe("DiamondAccessControl", () => { }); it("should not revoke role if not admin", async () => { - await expect(access.connect(SECOND).revokeRole(AGENT_ROLE, SECOND)).to.be.revertedWith( - await hasRoleErrorMessage(SECOND, ADMIN_ROLE), - ); + await expect(access.connect(SECOND).revokeRole(AGENT_ROLE, SECOND)) + .to.be.revertedWithCustomError(access, "RoleNotGranted") + .withArgs(await access.getRoleAdmin(AGENT_ROLE), SECOND); }); it("should not revoke role if it's not granted", async () => { await access.revokeRole(AGENT_ROLE, SECOND); - await expect(access.revokeRole(AGENT_ROLE, SECOND)).to.be.revertedWith("AccessControl: role is not granted"); + await expect(access.revokeRole(AGENT_ROLE, SECOND)) + .to.be.revertedWithCustomError(access, "RoleNotGranted") + .withArgs(AGENT_ROLE, SECOND); }); it("should revoke role if all conditions are met", async () => { @@ -119,9 +119,9 @@ describe("DiamondAccessControl", () => { }); it("should not renounce role if not self", async () => { - await expect(access.renounceRole(AGENT_ROLE, SECOND)).to.be.revertedWith( - "AccessControl: can only renounce roles for self", - ); + await expect(access.renounceRole(AGENT_ROLE, SECOND)) + .to.be.revertedWithCustomError(access, "UnauthorizedAccount") + .withArgs(OWNER); }); it("should renounce role if all conditions are met", async () => { @@ -138,26 +138,26 @@ describe("DiamondAccessControl", () => { }); it("should not grant role if not admin", async () => { - await expect(access.grantRole(AGENT_ROLE, THIRD)).to.be.revertedWith( - await hasRoleErrorMessage(OWNER, AGENT_ROLE), - ); - }); + await expect(access.grantRole(AGENT_ROLE, THIRD)) + .to.be.revertedWithCustomError(access, "RoleNotGranted") + .withArgs(await access.getRoleAdmin(AGENT_ROLE), OWNER); - it("should grant role if all conditions are met", async () => { - await access.connect(SECOND).grantRole(AGENT_ROLE, THIRD); + it("should grant role if all conditions are met", async () => { + await access.connect(SECOND).grantRole(AGENT_ROLE, THIRD); - expect(await access.hasRole(AGENT_ROLE, THIRD)).to.be.true; + expect(await access.hasRole(AGENT_ROLE, THIRD)).to.be.true; + }); }); }); - }); - describe("getters", () => { - it("should return base data", async () => { - expect(await access.DEFAULT_ADMIN_ROLE()).to.equal(ADMIN_ROLE); - expect(await access.AGENT_ROLE()).to.equal(AGENT_ROLE); - expect(await access.hasRole(ADMIN_ROLE, OWNER)).to.be.true; - expect(await access.hasRole(ADMIN_ROLE, SECOND)).to.be.false; - expect(await access.getRoleAdmin(AGENT_ROLE)).to.equal(ADMIN_ROLE); + describe("getters", () => { + it("should return base data", async () => { + expect(await access.DEFAULT_ADMIN_ROLE()).to.equal(ADMIN_ROLE); + expect(await access.AGENT_ROLE()).to.equal(AGENT_ROLE); + expect(await access.hasRole(ADMIN_ROLE, OWNER)).to.be.true; + expect(await access.hasRole(ADMIN_ROLE, SECOND)).to.be.false; + expect(await access.getRoleAdmin(AGENT_ROLE)).to.equal(ADMIN_ROLE); + }); }); }); }); diff --git a/test/diamond/DiamondERC165.test.ts b/test/diamond/DiamondERC165.test.ts index daeacbdc..297ed48d 100644 --- a/test/diamond/DiamondERC165.test.ts +++ b/test/diamond/DiamondERC165.test.ts @@ -1,5 +1,6 @@ import { ethers } from "hardhat"; import { expect } from "chai"; + import { Reverter } from "@/test/helpers/reverter"; import { getSelectors, FacetAction } from "@/test/helpers/diamond-helper"; diff --git a/test/diamond/DiamondERC20.test.ts b/test/diamond/DiamondERC20.test.ts index 1e9461b7..4c479428 100644 --- a/test/diamond/DiamondERC20.test.ts +++ b/test/diamond/DiamondERC20.test.ts @@ -1,9 +1,10 @@ import { ethers } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { expect } from "chai"; + +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; + import { Reverter } from "@/test/helpers/reverter"; import { getSelectors, FacetAction } from "@/test/helpers/diamond-helper"; -import { ZERO_ADDR, MAX_UINT256 } from "@/scripts/utils/constants"; import { wei } from "@/scripts/utils/utils"; import { OwnableDiamondMock, DiamondERC20Mock, Diamond } from "@ethers-v6"; @@ -48,28 +49,88 @@ describe("DiamondERC20 and InitializableStorage", () => { describe("access", () => { it("should initialize only once", async () => { - await expect(erc20.__DiamondERC20Mock_init("Mock Token", "MT")).to.be.revertedWith( - "Initializable: contract is already initialized", - ); + await expect(erc20.__DiamondERC20Mock_init("Mock Token", "MT")) + .to.be.revertedWithCustomError(erc20, "AlreadyInitialized") + .withArgs(); }); it("should initialize only by top level contract", async () => { - await expect(erc20.__DiamondERC20Direct_init("Mock Token", "MT")).to.be.revertedWith( - "Initializable: contract is not initializing", - ); + await expect(erc20.__DiamondERC20Direct_init("Mock Token", "MT")) + .to.be.revertedWithCustomError(erc20, "NotInitializing") + .withArgs(); + }); + + it("should reinitialize contract correctly", async () => { + await erc20.enableInitializers(1); + + let tx = erc20.__DiamondERC20Mock_reinit("Mock Token 2", "MT2", 2); + await expect(tx) + .to.emit(erc20, "Initialized") + .withArgs(await erc20.DIAMOND_ERC20_STORAGE_SLOT(), 2); + expect(await erc20.getInitializedVersion()).to.be.equal(2); + + tx = erc20.__DiamondERC20Mock_reinit("Mock Token 5", "MT5", 5); + await expect(tx) + .to.emit(erc20, "Initialized") + .withArgs(await erc20.DIAMOND_ERC20_STORAGE_SLOT(), 5); + expect(await erc20.getInitializedVersion()).to.be.equal(5); + + await expect(erc20.__DiamondERC20Mock_reinit("Mock Token 4", "MT4", 4)) + .to.be.revertedWithCustomError(erc20, "InvalidInitialization") + .withArgs(); + + expect(await erc20.getInitializedVersion()).to.be.equal(5); + + await expect(erc20.__DiamondERC20Mock_reinit("Mock Token 5", "MT5", 5)) + .to.be.revertedWithCustomError(erc20, "InvalidInitialization") + .withArgs(); + }); + + it("should not allow to reinitialize within the initializer", async () => { + const DiamondERC20Mock = await ethers.getContractFactory("DiamondERC20Mock"); + const contract = await DiamondERC20Mock.deploy(); + + await contract.enableInitializers(0); + + await expect(contract.__DiamondERC20Mock_reinitInit("Mock Token", "MTT", 2)) + .to.be.revertedWithCustomError(erc20, "InvalidInitialization") + .withArgs(); }); it("should disable implementation initialization", async () => { const DiamondERC20Mock = await ethers.getContractFactory("DiamondERC20Mock"); const contract = await DiamondERC20Mock.deploy(); - let tx = contract.deploymentTransaction(); + const deploymentTx = contract.deploymentTransaction(); - expect(tx) + expect(deploymentTx) .to.emit(contract, "Initialized") .withArgs(await erc20.DIAMOND_ERC20_STORAGE_SLOT()); - await expect(contract.disableInitializers()).to.be.revertedWith("Initializable: contract is initializing"); + await contract.enableInitializers(1); + + let disableTx = contract.disableInitializers(); + await expect(disableTx) + .to.emit(contract, "Initialized") + .withArgs(await erc20.DIAMOND_ERC20_STORAGE_SLOT(), 2n ** 64n - 1n); + + await expect(contract.__DiamondERC20Mock_reinit("Mock Token", "MTT", 2)) + .to.be.revertedWithCustomError(erc20, "InvalidInitialization") + .withArgs(); + + disableTx = contract.disableInitializers(); + await expect(disableTx).to.not.emit(contract, "Initialized"); + }); + + it("should not allow to disable initialization within the initializer", async () => { + const DiamondERC20Mock = await ethers.getContractFactory("DiamondERC20Mock"); + const contract = await DiamondERC20Mock.deploy(); + + await contract.enableInitializers(0); + + await expect(contract.__DiamondERC20Mock_disableInit()) + .to.be.revertedWithCustomError(erc20, "InvalidInitialization") + .withArgs(); }); }); @@ -83,18 +144,19 @@ describe("DiamondERC20 and InitializableStorage", () => { }); it("should not transfer tokens to/from zero address", async () => { - await expect(erc20.transferMock(SECOND.address, ZERO_ADDR, wei("100"))).to.be.revertedWith( - "ERC20: transfer to the zero address", - ); - await expect(erc20.transferMock(ZERO_ADDR, SECOND.address, wei("100"))).to.be.revertedWith( - "ERC20: transfer from the zero address", - ); + await expect(erc20.transferMock(SECOND.address, ethers.ZeroAddress, wei("100"))) + .to.be.revertedWithCustomError(erc20, "ReceiverIsZeroAddress") + .withArgs(); + + await expect(erc20.transferMock(ethers.ZeroAddress, SECOND.address, wei("100"))) + .to.be.revertedWithCustomError(erc20, "SenderIsZeroAddress") + .withArgs(); }); it("should not transfer tokens if balance is insufficient", async () => { - await expect(erc20.transfer(SECOND.address, wei("100"))).to.be.revertedWith( - "ERC20: transfer amount exceeds balance", - ); + await expect(erc20.transfer(SECOND.address, wei("100"))) + .to.be.revertedWithCustomError(erc20, "InsufficientBalance") + .withArgs(OWNER.address, erc20.balanceOf(OWNER.address), wei("100")); }); it("should mint tokens", async () => { @@ -104,7 +166,9 @@ describe("DiamondERC20 and InitializableStorage", () => { }); it("should not mint tokens to zero address", async () => { - await expect(erc20.mint(ZERO_ADDR, wei("100"))).to.be.revertedWith("ERC20: mint to the zero address"); + await expect(erc20.mint(ethers.ZeroAddress, wei("100"))) + .to.be.revertedWithCustomError(erc20, "ReceiverIsZeroAddress") + .withArgs(); }); it("should burn tokens", async () => { @@ -115,11 +179,15 @@ describe("DiamondERC20 and InitializableStorage", () => { }); it("should not burn tokens from zero address", async () => { - await expect(erc20.burn(ZERO_ADDR, wei("100"))).to.be.revertedWith("ERC20: burn from the zero address"); + await expect(erc20.burn(ethers.ZeroAddress, wei("100"))) + .to.be.revertedWithCustomError(erc20, "SenderIsZeroAddress") + .withArgs(); }); it("should not burn tokens if balance is insufficient", async () => { - await expect(erc20.burn(OWNER.address, wei("100"))).to.be.revertedWith("ERC20: burn amount exceeds balance"); + await expect(erc20.burn(OWNER.address, wei("100"))) + .to.be.revertedWithCustomError(erc20, "InsufficientBalance") + .withArgs(OWNER.address, erc20.balanceOf(OWNER.address), wei("100")); }); it("should approve tokens", async () => { @@ -129,12 +197,13 @@ describe("DiamondERC20 and InitializableStorage", () => { }); it("should not approve tokens to/from zero address", async () => { - await expect(erc20.approveMock(OWNER.address, ZERO_ADDR, wei("100"))).to.be.revertedWith( - "ERC20: approve to the zero address", - ); - await expect(erc20.approveMock(ZERO_ADDR, OWNER.address, wei("100"))).to.be.revertedWith( - "ERC20: approve from the zero address", - ); + await expect(erc20.approveMock(OWNER.address, ethers.ZeroAddress, wei("100"))) + .to.be.revertedWithCustomError(erc20, "SpenderIsZeroAddress") + .withArgs(); + + await expect(erc20.approveMock(ethers.ZeroAddress, OWNER.address, wei("100"))) + .to.be.revertedWithCustomError(erc20, "ApproverIsZeroAddress") + .withArgs(); }); it("should transfer tokens from address", async () => { @@ -150,17 +219,17 @@ describe("DiamondERC20 and InitializableStorage", () => { await erc20.mint(OWNER.address, wei("100")); await erc20.approve(SECOND.address, wei("100")); - await expect(erc20.connect(SECOND).transferFrom(OWNER.address, SECOND.address, wei("110"))).to.be.revertedWith( - "ERC20: insufficient allowance", - ); + await expect(erc20.connect(SECOND).transferFrom(OWNER.address, SECOND.address, wei("110"))) + .to.be.revertedWithCustomError(erc20, "InsufficientAllowance") + .withArgs(SECOND.address, await erc20.allowance(OWNER.address, SECOND.address), wei("110")); }); it("should not spend allowance if allowance is infinite type(uint256).max", async () => { await erc20.mint(OWNER.address, wei("100")); - await erc20.approve(SECOND, MAX_UINT256); + await erc20.approve(SECOND, ethers.MaxUint256); await erc20.connect(SECOND).transferFrom(OWNER.address, SECOND.address, wei("100")); - expect(await erc20.allowance(OWNER.address, SECOND.address)).to.equal(MAX_UINT256); + expect(await erc20.allowance(OWNER.address, SECOND.address)).to.equal(ethers.MaxUint256); }); }); diff --git a/test/diamond/DiamondERC721.test.ts b/test/diamond/DiamondERC721.test.ts index d2438707..444cb1dc 100644 --- a/test/diamond/DiamondERC721.test.ts +++ b/test/diamond/DiamondERC721.test.ts @@ -1,11 +1,12 @@ import { ethers } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { expect } from "chai"; + +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; + import { Reverter } from "@/test/helpers/reverter"; import { getSelectors, FacetAction } from "@/test/helpers/diamond-helper"; -import { ZERO_ADDR } from "@/scripts/utils/constants"; -import { OwnableDiamondMock, DiamondERC721Mock, Diamond } from "@ethers-v6"; +import { OwnableDiamondMock, DiamondERC721Mock, Diamond, DiamondERC721NotReceiverMock } from "@ethers-v6"; describe("DiamondERC721 and InitializableStorage", () => { const reverter = new Reverter(); @@ -16,15 +17,19 @@ describe("DiamondERC721 and InitializableStorage", () => { let erc721: DiamondERC721Mock; let diamond: OwnableDiamondMock; + let notReceiverMock: DiamondERC721NotReceiverMock; before("setup", async () => { [OWNER, SECOND, THIRD] = await ethers.getSigners(); const OwnableDiamond = await ethers.getContractFactory("OwnableDiamondMock"); const DiamondERC721Mock = await ethers.getContractFactory("DiamondERC721Mock"); + const DiamondERC721NotReceiverMock = await ethers.getContractFactory("DiamondERC721NotReceiverMock"); diamond = await OwnableDiamond.deploy(); + const diamond2 = await OwnableDiamond.deploy(); erc721 = await DiamondERC721Mock.deploy(); + notReceiverMock = await DiamondERC721NotReceiverMock.deploy(); const facets: Diamond.FacetStruct[] = [ { @@ -34,12 +39,25 @@ describe("DiamondERC721 and InitializableStorage", () => { }, ]; + const facets2: Diamond.FacetStruct[] = [ + { + facetAddress: await notReceiverMock.getAddress(), + action: FacetAction.Add, + functionSelectors: getSelectors(notReceiverMock.interface), + }, + ]; + await diamond.__OwnableDiamondMock_init(); await diamond.diamondCutShort(facets); + await diamond2.__OwnableDiamondMock_init(); + await diamond2.diamondCutShort(facets2); + erc721 = DiamondERC721Mock.attach(await diamond.getAddress()); + notReceiverMock = DiamondERC721NotReceiverMock.attach(await diamond2.getAddress()); await erc721.__DiamondERC721Mock_init("Mock Token", "MT"); + await notReceiverMock.__DiamondERC721Mock_init("Mock Token", "MT"); await reverter.snapshot(); }); @@ -48,28 +66,88 @@ describe("DiamondERC721 and InitializableStorage", () => { describe("access", () => { it("should initialize only once", async () => { - await expect(erc721.__DiamondERC721Mock_init("Mock Token", "MT")).to.be.revertedWith( - "Initializable: contract is already initialized", - ); + await expect(erc721.__DiamondERC721Mock_init("Mock Token", "MT")) + .to.be.revertedWithCustomError(erc721, "AlreadyInitialized") + .withArgs(); }); it("should initialize only by top level contract", async () => { - await expect(erc721.__DiamondERC721Direct_init("Mock Token", "MT")).to.be.revertedWith( - "Initializable: contract is not initializing", - ); + await expect(erc721.__DiamondERC721Direct_init("Mock Token", "MT")) + .to.be.revertedWithCustomError(erc721, "NotInitializing") + .withArgs(); + }); + + it("should reinitialize contract correctly", async () => { + await erc721.enableInitializers(1); + + let tx = erc721.__DiamondERC721Mock_reinit("Mock Token 2", "MT2", 2); + await expect(tx) + .to.emit(erc721, "Initialized") + .withArgs(await erc721.DIAMOND_ERC721_STORAGE_SLOT(), 2); + expect(await erc721.getInitializedVersion()).to.be.equal(2); + + tx = erc721.__DiamondERC721Mock_reinit("Mock Token 4", "MT4", 4); + await expect(tx) + .to.emit(erc721, "Initialized") + .withArgs(await erc721.DIAMOND_ERC721_STORAGE_SLOT(), 4); + expect(await erc721.getInitializedVersion()).to.be.equal(4); + + await expect(erc721.__DiamondERC721Mock_reinit("Mock Token 3", "MT3", 3)) + .to.be.revertedWithCustomError(erc721, "InvalidInitialization") + .withArgs(); + + expect(await erc721.getInitializedVersion()).to.be.equal(4); + + await expect(erc721.__DiamondERC721Mock_reinit("Mock Token 4", "MT4", 4)) + .to.be.revertedWithCustomError(erc721, "InvalidInitialization") + .withArgs(); + }); + + it("should not allow to reinitialize within the initializer", async () => { + const DiamondERC721Mock = await ethers.getContractFactory("DiamondERC721Mock"); + const contract = await DiamondERC721Mock.deploy(); + + await contract.enableInitializers(0); + + await expect(contract.__DiamondERC721Mock_reinitInit("Mock Token", "MTT", 2)) + .to.be.revertedWithCustomError(erc721, "InvalidInitialization") + .withArgs(); }); it("should disable implementation initialization", async () => { const DiamondERC721Mock = await ethers.getContractFactory("DiamondERC721Mock"); const contract = await DiamondERC721Mock.deploy(); - let tx = contract.deploymentTransaction(); + const deploymentTx = contract.deploymentTransaction(); - expect(tx) + expect(deploymentTx) .to.emit(contract, "Initialized") .withArgs(await erc721.DIAMOND_ERC721_STORAGE_SLOT()); - await expect(contract.disableInitializers()).to.be.revertedWith("Initializable: contract is initializing"); + await contract.enableInitializers(1); + + let disableTx = contract.disableInitializers(); + await expect(disableTx) + .to.emit(contract, "Initialized") + .withArgs(await erc721.DIAMOND_ERC721_STORAGE_SLOT(), 2n ** 64n - 1n); + + await expect(contract.__DiamondERC721Mock_reinit("Mock Token", "MTT", 2)) + .to.be.revertedWithCustomError(erc721, "InvalidInitialization") + .withArgs(); + + disableTx = contract.disableInitializers(); + await expect(disableTx).to.not.emit(contract, "Initialized"); + }); + + it("should not allow to disable initialization within the initializer", async () => { + const DiamondERC721Mock = await ethers.getContractFactory("DiamondERC721Mock"); + const contract = await DiamondERC721Mock.deploy(); + + await contract.enableInitializers(0); + + await expect(contract.__DiamondERC721Mock_disableInit()) + .to.be.revertedWithCustomError(erc721, "InvalidInitialization") + .withArgs(); }); }); @@ -87,16 +165,17 @@ describe("DiamondERC721 and InitializableStorage", () => { expect(await erc721.tokenByIndex(0)).to.equal(1); expect(await erc721.ownerOf(1)).to.equal(OWNER.address); - await expect(erc721.tokenOfOwnerByIndex(OWNER.address, 10)).to.be.revertedWith( - "ERC721Enumerable: owner index out of bounds", - ); - await expect(erc721.tokenByIndex(10)).to.be.revertedWith("ERC721Enumerable: global index out of bounds"); + await expect(erc721.tokenOfOwnerByIndex(OWNER.address, 10)) + .to.be.revertedWithCustomError(erc721, "OwnerIndexOutOfBounds") + .withArgs(OWNER.address, 10); + + await expect(erc721.tokenByIndex(10)).to.be.revertedWithCustomError(erc721, "IndexOutOfBounds").withArgs(10); expect(await erc721.tokenURI(1)).to.equal(""); await erc721.setBaseURI("https://example.com/"); expect(await erc721.tokenURI(1)).to.equal("https://example.com/1"); - await expect(erc721.tokenURI(10)).to.be.revertedWith("ERC721: invalid token ID"); + await expect(erc721.tokenURI(10)).to.be.revertedWithCustomError(erc721, "NonexistentToken").withArgs(10); }); it("should support all necessary interfaces", async () => { @@ -116,37 +195,46 @@ describe("DiamondERC721 and InitializableStorage", () => { it("should mint tokens", async () => { const tx = erc721.mint(OWNER.address, 1); - await expect(tx).to.emit(erc721, "Transfer").withArgs(ZERO_ADDR, OWNER.address, 1); + await expect(tx).to.emit(erc721, "Transfer").withArgs(ethers.ZeroAddress, OWNER.address, 1); expect(await erc721.balanceOf(OWNER.address)).to.equal(1); }); it("should not mint tokens to zero address", async () => { - await expect(erc721.mint(ZERO_ADDR, 1)).to.be.revertedWith("ERC721: mint to the zero address"); + await expect(erc721.mint(ethers.ZeroAddress, 1)).to.be.revertedWithCustomError(erc721, "ReceiverIsZeroAddress"); }); it("should not mint tokens if it's alredy minted", async () => { await erc721.mint(OWNER.address, 1); - await expect(erc721.mint(OWNER.address, 1)).to.be.revertedWith("ERC721: token already minted"); + await expect(erc721.mint(OWNER.address, 1)) + .to.be.revertedWithCustomError(erc721, "TokenAlreadyMinted") + .withArgs(1); }); - it("should not mint tokens if token is minted after `_beforeTokenTransfer` hook", async () => { + it("should not mint tokens if token is minted after `_update` hook", async () => { await erc721.toggleReplaceOwner(); - await expect(erc721.mint(OWNER.address, 1)).to.be.revertedWith("ERC721: token already minted"); + await expect(erc721.mint(OWNER.address, 1)) + .to.be.revertedWithCustomError(erc721, "TokenAlreadyMinted") + .withArgs(1); }); it("should not mint token if the receiver is a contract and doesn't implement onERC721Received correctly", async () => { const contract1 = await (await ethers.getContractFactory("DiamondERC721Mock")).deploy(); - await expect(erc721.mint(await contract1.getAddress(), 1)).to.be.revertedWith( - "ERC721: transfer to non ERC721Receiver implementer", - ); + await expect(erc721.mint(await contract1.getAddress(), 1)) + .to.be.revertedWithCustomError(erc721, "NonERC721Receiver") + .withArgs(await contract1.getAddress()); + + await expect(notReceiverMock.mint(await contract1.getAddress(), 1)) + .to.be.revertedWithCustomError(notReceiverMock, "NonERC721Receiver") + .withArgs(await contract1.getAddress()); const contract2 = await (await ethers.getContractFactory("NonERC721Receiver")).deploy(); - await expect(erc721.mint(await contract2.getAddress(), 1)).to.be.revertedWith( - "ERC721Receiver: reverting onERC721Received", + await expect(erc721.mint(await contract2.getAddress(), 1)).to.be.revertedWithCustomError( + contract2, + "RevertingOnERC721Received", ); }); }); @@ -159,25 +247,23 @@ describe("DiamondERC721 and InitializableStorage", () => { const tx = erc721.burn(1); - await expect(tx).to.emit(erc721, "Transfer").withArgs(OWNER.address, ZERO_ADDR, 1); + await expect(tx).to.emit(erc721, "Transfer").withArgs(OWNER.address, ethers.ZeroAddress, 1); expect(await erc721.balanceOf(OWNER.address)).to.equal(0); }); - it("should not burn an incorrect token", async () => { - await expect(erc721.burn(1)).to.be.revertedWith("ERC721: invalid token ID"); + it("should not burn a not minted token", async () => { + await expect(erc721.burn(1)).to.be.revertedWithCustomError(erc721, "NonexistentToken").withArgs(1); }); }); - describe("before token transfer hook", () => { - it("before token transfer hook should only accept one token", async () => { - expect(await erc721.beforeTokenTransfer(1)).not.to.be.reverted; + describe("update hook", () => { + it("update hook should only accept one token", async () => { + expect(await erc721.update(1)).not.to.be.reverted; }); - it("before token transfer hook should not accept more than one token", async () => { - await expect(erc721.beforeTokenTransfer(2)).to.be.revertedWith( - "ERC721Enumerable: consecutive transfers not supported", - ); + it("update hook should not accept more than one token", async () => { + await expect(erc721.update(2)).to.be.revertedWithCustomError(erc721, "ConsecutiveTransfersNotSupported"); }); }); @@ -217,7 +303,7 @@ describe("DiamondERC721 and InitializableStorage", () => { expect(await erc721.balanceOf(OWNER.address)).to.equal(1); expect(await erc721.balanceOf(SECOND.address)).to.equal(0); - const receiver = await (await ethers.getContractFactory("ERC721Holder")).deploy(); + const receiver = await (await ethers.getContractFactory("ERC721HolderMock")).deploy(); const tx = erc721.safeTransferFromMock(OWNER.address, await receiver.getAddress(), 1); await expect(tx) @@ -231,48 +317,57 @@ describe("DiamondERC721 and InitializableStorage", () => { it("should not transfer tokens when caller is not an owner or not approved", async () => { await erc721.mint(OWNER.address, 1); - await expect(erc721.connect(SECOND).transferFrom(OWNER.address, SECOND.address, 1)).to.be.revertedWith( - "ERC721: caller is not token owner or approved", - ); - await expect(erc721.connect(SECOND).safeTransferFromMock(OWNER.address, SECOND.address, 1)).to.be.revertedWith( - "ERC721: caller is not token owner or approved", - ); + await expect(erc721.connect(SECOND).transferFrom(OWNER.address, SECOND.address, 1)) + .to.be.revertedWithCustomError(erc721, "InvalidSpender") + .withArgs(SECOND.address, 1); + + await expect(erc721.connect(SECOND).safeTransferFromMock(OWNER.address, SECOND.address, 1)) + .to.be.revertedWithCustomError(erc721, "InvalidSpender") + .withArgs(SECOND.address, 1); }); it("should not transfer tokens when call is not an owner", async () => { await erc721.mint(OWNER.address, 1); - await expect(erc721.transferFromMock(SECOND.address, OWNER.address, 1)).to.be.revertedWith( - "ERC721: transfer from incorrect owner", - ); + await expect(erc721.transferFromMock(SECOND.address, OWNER.address, 1)) + .to.be.revertedWithCustomError(erc721, "UnauthorizedAccount") + .withArgs(SECOND.address); }); it("should not transfer tokens to zero address", async () => { await erc721.mint(OWNER.address, 1); - await expect(erc721.transferFromMock(OWNER.address, ZERO_ADDR, 1)).to.be.revertedWith( - "ERC721: transfer to the zero address", + await expect(erc721.transferFromMock(OWNER.address, ethers.ZeroAddress, 1)).to.be.revertedWithCustomError( + erc721, + "ReceiverIsZeroAddress", ); }); - it("should not transfer tokens if owner is changed after `_beforeTokenTransfer` hook", async () => { + it("should not transfer tokens if owner is changed after `_update` hook", async () => { await erc721.mint(OWNER.address, 1); await erc721.toggleReplaceOwner(); - await expect(erc721.transferFromMock(OWNER.address, SECOND.address, 1)).to.be.revertedWith( - "ERC721: transfer from incorrect owner", + await expect(erc721.transferFromMock(OWNER.address, SECOND.address, 1)).to.be.revertedWithCustomError( + erc721, + "UnauthorizedAccount", ); }); it("should not transfer token if the receiver is a contract and doesn't implement onERC721Received", async () => { - await erc721.mint(OWNER.address, 1); + await notReceiverMock.mockMint(OWNER.address, 1); const contract = await (await ethers.getContractFactory("DiamondERC721Mock")).deploy(); - await expect(erc721.safeTransferFromMock(OWNER.address, await contract.getAddress(), 1)).to.be.revertedWith( - "ERC721: transfer to non ERC721Receiver implementer", - ); + await expect(notReceiverMock.safeTransferFromMock(OWNER.address, await contract.getAddress(), 1)) + .to.be.revertedWithCustomError(notReceiverMock, "NonERC721Receiver") + .withArgs(await contract.getAddress()); + }); + + it("should not transfer incorrect token", async () => { + await expect(erc721.transferFromMock(OWNER.address, SECOND.address, 1)) + .to.be.revertedWithCustomError(erc721, "NonexistentToken") + .withArgs(1); }); }); @@ -298,20 +393,24 @@ describe("DiamondERC721 and InitializableStorage", () => { }); it("should not approve incorrect token", async () => { - await expect(erc721.approve(OWNER.address, 1)).to.be.revertedWith("ERC721: invalid token ID"); + await expect(erc721.approve(OWNER.address, 1)) + .to.be.revertedWithCustomError(erc721, "NonexistentToken") + .withArgs(1); }); it("should not approve token if caller is not an owner", async () => { await erc721.mint(OWNER.address, 1); - await expect(erc721.connect(SECOND).approve(THIRD.address, 1)).to.be.revertedWith( - "ERC721: approve caller is not token owner or approved for all", - ); + await expect(erc721.connect(SECOND).approve(THIRD.address, 1)) + .to.be.revertedWithCustomError(erc721, "InvalidApprover") + .withArgs(SECOND.address, OWNER.address); }); it("should not approve token if spender and caller are the same", async () => { await erc721.mint(OWNER.address, 1); - await expect(erc721.approve(OWNER.address, 1)).to.be.revertedWith("ERC721: approval to current owner"); + await expect(erc721.approve(OWNER.address, 1)) + .to.be.revertedWithCustomError(erc721, "ApprovalToCurrentOwner") + .withArgs(OWNER.address, 1); }); it("should approve all tokens", async () => { @@ -332,7 +431,9 @@ describe("DiamondERC721 and InitializableStorage", () => { await erc721.mint(OWNER.address, 2); await erc721.mint(OWNER.address, 3); - await expect(erc721.setApprovalForAll(OWNER.address, true)).to.be.revertedWith("ERC721: approve to caller"); + await expect(erc721.setApprovalForAll(OWNER.address, true)) + .to.be.revertedWithCustomError(erc721, "ApproveToCaller") + .withArgs(OWNER.address); }); }); }); diff --git a/test/finance/compound-rate-keeper/CompoundRateKeeper.test.ts b/test/finance/compound-rate-keeper/CompoundRateKeeper.test.ts index b0f56212..347441af 100644 --- a/test/finance/compound-rate-keeper/CompoundRateKeeper.test.ts +++ b/test/finance/compound-rate-keeper/CompoundRateKeeper.test.ts @@ -1,7 +1,9 @@ import { ethers } from "hardhat"; +import { expect } from "chai"; + import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { time } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; + import { Reverter } from "@/test/helpers/reverter"; import { precision } from "@/scripts/utils/utils"; @@ -31,22 +33,20 @@ describe("CompoundRateKeeper", () => { describe("access", () => { it("should not initialize twice", async () => { - await expect(keeper.mockInit(precision(1), 31536000)).to.be.revertedWith( - "Initializable: contract is not initializing", - ); - await expect(keeper.__OwnableCompoundRateKeeper_init(precision(1), 31536000)).to.be.revertedWith( - "Initializable: contract is already initialized", - ); + await expect(keeper.mockInit(precision(1), 31536000)).to.be.revertedWithCustomError(keeper, "NotInitializing"); + await expect(keeper.__OwnableCompoundRateKeeper_init(precision(1), 31536000)) + .to.be.revertedWithCustomError(keeper, "InvalidInitialization") + .withArgs(); }); it("only owner should call these functions", async () => { - await expect(keeper.connect(SECOND).setCapitalizationRate(precision(1))).to.be.revertedWith( - "Ownable: caller is not the owner", - ); + await expect(keeper.connect(SECOND).setCapitalizationRate(precision(1))) + .to.be.revertedWithCustomError(keeper, "OwnableUnauthorizedAccount") + .withArgs(SECOND); - await expect(keeper.connect(SECOND).setCapitalizationPeriod(31536000)).to.be.revertedWith( - "Ownable: caller is not the owner", - ); + await expect(keeper.connect(SECOND).setCapitalizationPeriod(31536000)) + .to.be.revertedWithCustomError(keeper, "OwnableUnauthorizedAccount") + .withArgs(SECOND); }); }); @@ -73,7 +73,9 @@ describe("CompoundRateKeeper", () => { }); it("should revert if rate is less than zero", async () => { - await expect(keeper.setCapitalizationRate(precision(0.99999))).to.be.revertedWith("CRK: rate is less than 1"); + await expect(keeper.setCapitalizationRate(precision(0.99999))) + .to.be.revertedWithCustomError(keeper, "RateIsLessThanOne") + .withArgs(precision(0.99999)); }); it("should revert if compound rate reaches max limit", async () => { @@ -83,7 +85,9 @@ describe("CompoundRateKeeper", () => { await time.setNextBlockTimestamp((await time.latest()) + 100 * 31536000); await keeper.emergencyUpdateCompoundRate(); - await expect(keeper.setCapitalizationRate(precision(1.1))).to.be.revertedWith("CRK: max rate is reached"); + await expect(keeper.setCapitalizationRate(precision(1.1))) + .to.be.revertedWithCustomError(keeper, "MaxRateIsReached") + .withArgs(); }); }); @@ -99,7 +103,9 @@ describe("CompoundRateKeeper", () => { }); it("should revert if capitalization period is zero", async () => { - await expect(keeper.setCapitalizationPeriod(0)).to.be.revertedWith("CRK: invalid period"); + await expect(keeper.setCapitalizationPeriod(0)) + .to.be.revertedWithCustomError(keeper, "CapitalizationPeriodIsZero") + .withArgs(); }); it("should revert if compound rate reaches max limit", async () => { @@ -109,7 +115,9 @@ describe("CompoundRateKeeper", () => { await time.setNextBlockTimestamp((await time.latest()) + 100 * 31536000); await keeper.emergencyUpdateCompoundRate(); - await expect(keeper.setCapitalizationPeriod(1)).to.be.revertedWith("CRK: max rate is reached"); + await expect(keeper.setCapitalizationPeriod(1)) + .to.be.revertedWithCustomError(keeper, "MaxRateIsReached") + .withArgs(); }); }); diff --git a/test/finance/staking/AbstractStaking.test.ts b/test/finance/staking/AbstractStaking.test.ts deleted file mode 100644 index adf8f8ea..00000000 --- a/test/finance/staking/AbstractStaking.test.ts +++ /dev/null @@ -1,762 +0,0 @@ -import { ethers } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; -import { time } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; -import { Reverter } from "@/test/helpers/reverter"; - -import { AbstractStakingMock, ERC20Mock } from "@ethers-v6"; -import { wei } from "@/scripts/utils/utils"; -import { ZERO_ADDR } from "@/scripts/utils/constants"; - -describe("AbstractStaking", () => { - const reverter = new Reverter(); - - let FIRST: SignerWithAddress; - let SECOND: SignerWithAddress; - let THIRD: SignerWithAddress; - - let sharesToken: ERC20Mock; - let rewardsToken: ERC20Mock; - - let sharesDecimals: number; - let rewardsDecimals: number; - - let stakingStartTime: bigint; - let rate: bigint; - - let abstractStaking: AbstractStakingMock; - - const mintAndApproveTokens = async (user: SignerWithAddress, token: ERC20Mock, amount: bigint) => { - await token.mint(user, amount); - await token.connect(user).approve(abstractStaking, amount); - }; - - const performStakingManipulations = async () => { - await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); - await mintAndApproveTokens(SECOND, sharesToken, wei(200, sharesDecimals)); - await mintAndApproveTokens(THIRD, sharesToken, wei(200, sharesDecimals)); - - await time.setNextBlockTimestamp((await time.latest()) + 2); - - await abstractStaking.connect(FIRST).stake(wei(100, sharesDecimals)); - - await time.setNextBlockTimestamp((await time.latest()) + 2); - - await abstractStaking.connect(SECOND).stake(wei(200, sharesDecimals)); - - await abstractStaking.connect(THIRD).stake(wei(100, sharesDecimals)); - - await time.setNextBlockTimestamp((await time.latest()) + 3); - - await abstractStaking.connect(FIRST).unstake(wei(100, sharesDecimals)); - - await abstractStaking.connect(THIRD).stake(wei(100, sharesDecimals)); - - await abstractStaking.connect(SECOND).unstake(wei(200, sharesDecimals)); - - await time.setNextBlockTimestamp((await time.latest()) + 2); - - await abstractStaking.connect(THIRD).unstake(wei(200, sharesDecimals)); - }; - - const checkManipulationRewards = async () => { - const firstExpectedReward = wei(3, rewardsDecimals) + wei(1, rewardsDecimals) / 12n; - const secondExpectedReward = wei(3, rewardsDecimals) + wei(1, rewardsDecimals) / 3n; - const thirdExpectedReward = wei(3, rewardsDecimals) + wei(7, rewardsDecimals) / 12n; - - expect(await abstractStaking.getOwedValue(FIRST)).to.equal(firstExpectedReward); - expect(await abstractStaking.getOwedValue(SECOND)).to.equal(secondExpectedReward); - expect(await abstractStaking.getOwedValue(THIRD)).to.equal(thirdExpectedReward); - - expect(await abstractStaking.userOwedValue(FIRST)).to.equal(firstExpectedReward); - expect(await abstractStaking.userOwedValue(SECOND)).to.equal(secondExpectedReward); - expect(await abstractStaking.userOwedValue(THIRD)).to.equal(thirdExpectedReward); - }; - - const performStakingManipulations2 = async () => { - await mintAndApproveTokens(FIRST, sharesToken, wei(400, sharesDecimals)); - await mintAndApproveTokens(SECOND, sharesToken, wei(100, sharesDecimals)); - await mintAndApproveTokens(THIRD, sharesToken, wei(400, sharesDecimals)); - - await abstractStaking.connect(FIRST).stake(wei(200, sharesDecimals)); - - await time.setNextBlockTimestamp((await time.latest()) + 3); - - await abstractStaking.connect(SECOND).stake(wei(100, sharesDecimals)); - - await abstractStaking.connect(THIRD).stake(wei(300, sharesDecimals)); - - await time.setNextBlockTimestamp((await time.latest()) + 3); - - await abstractStaking.connect(FIRST).stake(wei(200, sharesDecimals)); - - await abstractStaking.connect(FIRST).unstake(wei(100, sharesDecimals)); - - await time.setNextBlockTimestamp((await time.latest()) + 3); - - await abstractStaking.connect(SECOND).unstake(wei(100, sharesDecimals)); - - await abstractStaking.connect(THIRD).stake(wei(100, sharesDecimals)); - - await time.setNextBlockTimestamp((await time.latest()) + 2); - - await abstractStaking.connect(FIRST).unstake(wei(300, sharesDecimals)); - - await abstractStaking.connect(THIRD).unstake(wei(400, sharesDecimals)); - }; - - const checkManipulationRewards2 = async () => { - const firstExpectedReward = wei(7, rewardsDecimals) + wei(2, rewardsDecimals) / 3n + wei(1, rewardsDecimals) / 7n; - const secondExpectedReward = wei(233, rewardsDecimals) / 168n; - const thirdExpectedReward = wei(5, rewardsDecimals) + wei(3, rewardsDecimals) / 8n + wei(3, rewardsDecimals) / 7n; - - expect(await abstractStaking.getOwedValue(FIRST)).to.equal(firstExpectedReward); - expect(await abstractStaking.getOwedValue(SECOND)).to.equal(secondExpectedReward); - expect(await abstractStaking.getOwedValue(THIRD)).to.equal(thirdExpectedReward); - - expect(await abstractStaking.userOwedValue(FIRST)).to.equal(firstExpectedReward); - expect(await abstractStaking.userOwedValue(SECOND)).to.equal(secondExpectedReward); - expect(await abstractStaking.userOwedValue(THIRD)).to.equal(thirdExpectedReward); - }; - - before("setup", async () => { - [FIRST, SECOND, THIRD] = await ethers.getSigners(); - - const AbstractStakingMock = await ethers.getContractFactory("AbstractStakingMock"); - const ERC20Mock = await ethers.getContractFactory("ERC20Mock"); - - abstractStaking = await AbstractStakingMock.deploy(); - sharesToken = await ERC20Mock.deploy("SharesMock", "SMock", 18); - rewardsToken = await ERC20Mock.deploy("RewardsMock", "RMock", 18); - - sharesDecimals = Number(await sharesToken.decimals()); - rewardsDecimals = Number(await rewardsToken.decimals()); - - await rewardsToken.mint(await abstractStaking.getAddress(), wei(100, rewardsDecimals)); - - stakingStartTime = 3n; - rate = wei(1, rewardsDecimals); - - await abstractStaking.__AbstractStakingMock_init(sharesToken, rewardsToken, rate, stakingStartTime); - - await reverter.snapshot(); - }); - - afterEach(reverter.revert); - - describe("AbstractStaking initialization", () => { - it("should not initialize twice", async () => { - await expect(abstractStaking.mockInit(sharesToken, rewardsToken, rate, stakingStartTime)).to.be.revertedWith( - "Initializable: contract is not initializing", - ); - await expect( - abstractStaking.__AbstractStakingMock_init(sharesToken, rewardsToken, rate, stakingStartTime), - ).to.be.revertedWith("Initializable: contract is already initialized"); - }); - - it("should set the initial values correctly", async () => { - expect(await abstractStaking.sharesToken()).to.equal(await sharesToken.getAddress()); - expect(await abstractStaking.rewardsToken()).to.equal(await rewardsToken.getAddress()); - expect(await abstractStaking.rate()).to.equal(rate); - expect(await abstractStaking.stakingStartTime()).to.equal(stakingStartTime); - }); - - it("should not allow to set 0 as a Shares Token or Rewards Token", async () => { - const AbstractStakingMock = await ethers.getContractFactory("AbstractStakingMock"); - let abstractStaking = await AbstractStakingMock.deploy(); - - await expect( - abstractStaking.__AbstractStakingMock_init(ZERO_ADDR, rewardsToken, rate, stakingStartTime), - ).to.be.revertedWith("Staking: zero address cannot be the Shares Token"); - - await expect( - abstractStaking.__AbstractStakingMock_init(sharesToken, ZERO_ADDR, rate, stakingStartTime), - ).to.be.revertedWith("Staking: zero address cannot be the Rewards Token"); - }); - }); - - describe("timestamps", () => { - it("should not allow to stake, unstake, withdraw tokens or claim rewards before the start of the staking", async () => { - await abstractStaking.setStakingStartTime(1638474321); - - const revertMessage = "Staking: staking has not started yet"; - - await expect(abstractStaking.stake(wei(100, sharesDecimals))).to.be.revertedWith(revertMessage); - await expect(abstractStaking.unstake(wei(100, sharesDecimals))).to.be.revertedWith(revertMessage); - await expect(abstractStaking.withdraw()).to.be.revertedWith(revertMessage); - await expect(abstractStaking.claim(wei(100, sharesDecimals))).to.be.revertedWith(revertMessage); - await expect(abstractStaking.claimAll()).to.be.revertedWith(revertMessage); - }); - - it("should work as expected if the staking start time is set to the timestamp in the past", async () => { - await time.setNextBlockTimestamp((await time.latest()) + 20); - - await abstractStaking.setStakingStartTime(2); - - expect(await abstractStaking.stakingStartTime()).to.equal(2); - - await performStakingManipulations(); - - await checkManipulationRewards(); - }); - - it("should update values correctly if more than one transaction which updates the key values is sent within one block", async () => { - await mintAndApproveTokens(FIRST, sharesToken, wei(400, sharesDecimals)); - - await abstractStaking.connect(FIRST).stake(wei(100, sharesDecimals)); - await abstractStaking.connect(FIRST).stake(wei(100, sharesDecimals)); - - await abstractStaking.multicall([ - abstractStaking.interface.encodeFunctionData("stake", [wei(100, sharesDecimals)]), - abstractStaking.interface.encodeFunctionData("stake", [wei(100, sharesDecimals)]), - ]); - - expect(await abstractStaking.userShares(FIRST)).to.equal(wei(400, sharesDecimals)); - expect(await abstractStaking.cumulativeSum()).to.equal(wei(1.5, 23)); - }); - - it("should work as expected if more than one transaction which updates the key values is sent within one block", async () => { - const StakersFactory = await ethers.getContractFactory("StakersFactory"); - const stakersFactory = await StakersFactory.deploy(); - - await stakersFactory.createStaker(); - await stakersFactory.createStaker(); - - const staker1 = await stakersFactory.stakers(0); - const staker2 = await stakersFactory.stakers(1); - - await sharesToken.mint(staker1, wei(500, sharesDecimals)); - await sharesToken.mint(staker2, wei(500, sharesDecimals)); - await mintAndApproveTokens(THIRD, sharesToken, wei(100, sharesDecimals)); - - await stakersFactory.stake(abstractStaking, staker1, sharesToken, wei(100, sharesDecimals)); - - await stakersFactory.multicall([ - stakersFactory.interface.encodeFunctionData("stake", [ - await abstractStaking.getAddress(), - staker1, - await sharesToken.getAddress(), - wei(200, sharesDecimals), - ]), - stakersFactory.interface.encodeFunctionData("stake", [ - await abstractStaking.getAddress(), - staker2, - await sharesToken.getAddress(), - wei(200, sharesDecimals), - ]), - ]); - - await time.setNextBlockTimestamp((await time.latest()) + 3); - - await stakersFactory.unstake(abstractStaking, staker2, wei(100, sharesDecimals)); - - await time.setNextBlockTimestamp((await time.latest()) + 4); - - await abstractStaking.connect(THIRD).stake(wei(100, sharesDecimals)); - - await stakersFactory.multicall([ - stakersFactory.interface.encodeFunctionData("unstake", [ - await abstractStaking.getAddress(), - staker1, - wei(300, sharesDecimals), - ]), - stakersFactory.interface.encodeFunctionData("unstake", [ - await abstractStaking.getAddress(), - staker2, - wei(100, sharesDecimals), - ]), - ]); - - await time.setNextBlockTimestamp((await time.latest()) + 3); - - await abstractStaking.connect(THIRD).unstake(wei(100, sharesDecimals)); - - const firstExpectedReward = wei(6, rewardsDecimals) + wei(2, rewardsDecimals) / 5n; - const secondExpectedReward = wei(2, rewardsDecimals) + wei(2, rewardsDecimals) / 5n; - const thirdExpectedReward = wei(3, rewardsDecimals) + wei(1, rewardsDecimals) / 5n; - - expect(await abstractStaking.getOwedValue(staker1)).to.equal(firstExpectedReward); - expect(await abstractStaking.getOwedValue(staker2)).to.equal(secondExpectedReward); - expect(await abstractStaking.getOwedValue(THIRD)).to.equal(thirdExpectedReward); - - expect(await abstractStaking.userOwedValue(staker1)).to.equal(firstExpectedReward); - expect(await abstractStaking.userOwedValue(staker2)).to.equal(secondExpectedReward); - expect(await abstractStaking.userOwedValue(THIRD)).to.equal(thirdExpectedReward); - }); - }); - - describe("stake()", () => { - it("should add shares after staking correctly", async () => { - await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); - await mintAndApproveTokens(SECOND, sharesToken, wei(300, sharesDecimals)); - - await abstractStaking.connect(FIRST).stake(wei(100, sharesDecimals)); - await abstractStaking.connect(SECOND).stake(wei(300, sharesDecimals)); - - expect(await abstractStaking.totalShares()).to.equal(wei(400, sharesDecimals)); - expect(await abstractStaking.userShares(FIRST)).to.equal(wei(100, sharesDecimals)); - expect(await abstractStaking.userShares(SECOND)).to.equal(wei(300, sharesDecimals)); - }); - - it("should transfer tokens correctly on stake", async () => { - await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); - - await abstractStaking.connect(FIRST).stake(wei(50, sharesDecimals)); - - expect(await sharesToken.balanceOf(FIRST)).to.equal(wei(50, sharesDecimals)); - expect(await sharesToken.balanceOf(abstractStaking)).to.equal(wei(50, sharesDecimals)); - - await abstractStaking.connect(FIRST).stake(wei(50, sharesDecimals)); - - expect(await sharesToken.balanceOf(FIRST)).to.equal(0); - expect(await sharesToken.balanceOf(abstractStaking)).to.equal(wei(100, sharesDecimals)); - }); - - it("should not allow to stake 0 tokens", async () => { - await expect(abstractStaking.connect(FIRST).stake(0)).to.be.revertedWith( - "ValueDistributor: amount has to be more than 0", - ); - }); - }); - - describe("unstake()", () => { - it("should remove shares after unstaking correctly", async () => { - await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); - await mintAndApproveTokens(SECOND, sharesToken, wei(300, sharesDecimals)); - - await abstractStaking.connect(FIRST).stake(wei(100, sharesDecimals)); - await abstractStaking.connect(SECOND).stake(wei(300, sharesDecimals)); - - await abstractStaking.connect(FIRST).unstake(wei(50, sharesDecimals)); - await abstractStaking.connect(SECOND).unstake(wei(200, sharesDecimals)); - - expect(await abstractStaking.totalShares()).to.equal(wei(150, sharesDecimals)); - expect(await abstractStaking.userShares(FIRST)).to.equal(wei(50, sharesDecimals)); - expect(await abstractStaking.userShares(SECOND)).to.equal(wei(100, sharesDecimals)); - }); - - it("should handle unstaking the whole amount staked correctly", async () => { - await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); - await mintAndApproveTokens(SECOND, sharesToken, wei(200, sharesDecimals)); - - await abstractStaking.connect(FIRST).stake(wei(100, sharesDecimals)); - await abstractStaking.connect(SECOND).stake(wei(200, sharesDecimals)); - - await abstractStaking.connect(FIRST).unstake(wei(100, sharesDecimals)); - await abstractStaking.connect(SECOND).unstake(wei(200, sharesDecimals)); - - const cumulativeSum = await abstractStaking.cumulativeSum(); - - expect(await abstractStaking.totalShares()).to.equal(0); - expect(await abstractStaking.userShares(FIRST)).to.equal(0); - expect(await abstractStaking.userShares(SECOND)).to.equal(0); - - await sharesToken.connect(FIRST).approve(abstractStaking, wei(50, sharesDecimals)); - await sharesToken.connect(SECOND).approve(abstractStaking, wei(100, sharesDecimals)); - - await abstractStaking.connect(FIRST).stake(wei(50, sharesDecimals)); - - expect(await abstractStaking.cumulativeSum()).to.equal(cumulativeSum); - - await abstractStaking.connect(SECOND).stake(wei(100, sharesDecimals)); - - expect(await abstractStaking.totalShares()).to.equal(wei(150, sharesDecimals)); - expect(await abstractStaking.userShares(FIRST)).to.equal(wei(50, sharesDecimals)); - expect(await abstractStaking.userShares(SECOND)).to.equal(wei(100, sharesDecimals)); - }); - - it("should transfer tokens correctly on unstake", async () => { - await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); - - await abstractStaking.connect(FIRST).stake(wei(100, sharesDecimals)); - - await abstractStaking.connect(FIRST).unstake(wei(50, sharesDecimals)); - - expect(await sharesToken.balanceOf(FIRST)).to.equal(wei(50, sharesDecimals)); - expect(await sharesToken.balanceOf(abstractStaking)).to.equal(wei(50, sharesDecimals)); - - await abstractStaking.connect(FIRST).unstake(wei(50, sharesDecimals)); - - expect(await sharesToken.balanceOf(FIRST)).to.equal(wei(100, sharesDecimals)); - expect(await sharesToken.balanceOf(abstractStaking)).to.equal(0); - }); - - it("should not allow to unstake 0 tokens", async () => { - await expect(abstractStaking.connect(FIRST).unstake(0)).to.be.revertedWith( - "ValueDistributor: amount has to be more than 0", - ); - }); - - it("should not allow to unstake more than it was staked", async () => { - await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); - - await abstractStaking.connect(FIRST).stake(wei(100, sharesDecimals)); - await expect(abstractStaking.connect(FIRST).unstake(wei(150, sharesDecimals))).to.be.revertedWith( - "ValueDistributor: insufficient amount", - ); - }); - }); - - describe("withdraw()", () => { - it("should withdraw tokens correctly", async () => { - await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); - - await abstractStaking.connect(FIRST).stake(wei(100, sharesDecimals)); - - await abstractStaking.connect(FIRST).withdraw(); - - expect(await abstractStaking.totalShares()).to.equal(0); - expect(await abstractStaking.userShares(FIRST)).to.equal(0); - }); - - it("should transfer tokens correctly on withdraw", async () => { - await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); - - await abstractStaking.connect(FIRST).stake(wei(100, sharesDecimals)); - - await abstractStaking.connect(FIRST).withdraw(); - - expect(await sharesToken.balanceOf(abstractStaking)).to.equal(0); - expect(await sharesToken.balanceOf(FIRST)).to.equal(wei(100, sharesDecimals)); - }); - - it("should claim all the rewards earned after the withdrawal", async () => { - await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); - - await abstractStaking.connect(FIRST).stake(wei(100, sharesDecimals)); - - await time.setNextBlockTimestamp((await time.latest()) + 30); - - await abstractStaking.connect(FIRST).withdraw(); - - expect(await abstractStaking.getOwedValue(FIRST)).to.equal(0); - expect(await abstractStaking.userOwedValue(FIRST)).to.equal(0); - }); - - it("should work as expected if withdraw is called right after claiming all the rewards within one block", async () => { - await mintAndApproveTokens(SECOND, sharesToken, wei(200, sharesDecimals)); - await mintAndApproveTokens(FIRST, sharesToken, wei(200, sharesDecimals)); - - await abstractStaking.connect(FIRST).stake(wei(100, sharesDecimals)); - - // triggering the next block - await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); - - await abstractStaking.multicall([ - abstractStaking.interface.encodeFunctionData("claim", [await abstractStaking.getOwedValue(FIRST)]), - abstractStaking.interface.encodeFunctionData("withdraw"), - ]); - - expect(await abstractStaking.getOwedValue(FIRST)).to.equal(0); - expect(await abstractStaking.userOwedValue(FIRST)).to.equal(0); - expect(await abstractStaking.userShares(FIRST)).to.equal(0); - }); - - it("should withdraw as expected if there are no rewards because of the 0 rate", async () => { - await abstractStaking.setRate(0); - - await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); - - await abstractStaking.connect(FIRST).stake(wei(100, sharesDecimals)); - - await time.setNextBlockTimestamp((await time.latest()) + 30); - - await abstractStaking.withdraw(); - - expect(await abstractStaking.getOwedValue(FIRST)).to.equal(0); - expect(await abstractStaking.userOwedValue(FIRST)).to.equal(0); - expect(await abstractStaking.userShares(FIRST)).to.equal(0); - }); - - it("should not allow to withdraw if there are no shares", async () => { - await mintAndApproveTokens(FIRST, sharesToken, wei(200, sharesDecimals)); - - await abstractStaking.connect(FIRST).stake(wei(200, sharesDecimals)); - - await expect( - abstractStaking.multicall([ - abstractStaking.interface.encodeFunctionData("unstake", [wei(200, sharesDecimals)]), - abstractStaking.interface.encodeFunctionData("withdraw"), - ]), - ).to.be.revertedWith("ValueDistributor: amount has to be more than 0"); - }); - }); - - describe("claim()", () => { - it("should calculate the rewards earned for a user correctly", async () => { - await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); - - await abstractStaking.connect(FIRST).stake(wei(100, sharesDecimals)); - - await time.setNextBlockTimestamp((await time.latest()) + 30); - - await abstractStaking.connect(FIRST).unstake(wei(100, sharesDecimals)); - - expect(await abstractStaking.getOwedValue(FIRST)).to.equal(wei(30, rewardsDecimals)); - expect(await abstractStaking.userOwedValue(FIRST)).to.equal(wei(30, rewardsDecimals)); - }); - - it("should calculate the reward earned for multiple users correctly", async () => { - await performStakingManipulations(); - - await checkManipulationRewards(); - }); - - it("should calculate the reward earned for multiple users correctly", async () => { - await performStakingManipulations2(); - - await checkManipulationRewards2(); - }); - - it("should claim all the rewards correctly", async () => { - await performStakingManipulations(); - - await abstractStaking.connect(FIRST).claim(await abstractStaking.getOwedValue(FIRST)); - await abstractStaking.connect(SECOND).claim(await abstractStaking.getOwedValue(SECOND)); - await abstractStaking.connect(THIRD).claim(await abstractStaking.getOwedValue(THIRD)); - - expect(await abstractStaking.getOwedValue(FIRST)).to.equal(0); - expect(await abstractStaking.getOwedValue(SECOND)).to.equal(0); - expect(await abstractStaking.getOwedValue(THIRD)).to.equal(0); - - expect(await abstractStaking.userOwedValue(FIRST)).to.equal(0); - expect(await abstractStaking.userOwedValue(SECOND)).to.equal(0); - expect(await abstractStaking.userOwedValue(THIRD)).to.equal(0); - }); - - it("should correctly claim rewards partially", async () => { - await performStakingManipulations(); - - await abstractStaking.connect(FIRST).claim((await abstractStaking.getOwedValue(FIRST)) - wei(1, rewardsDecimals)); - await abstractStaking - .connect(SECOND) - .claim((await abstractStaking.getOwedValue(SECOND)) - wei(2, rewardsDecimals)); - await abstractStaking.connect(THIRD).claim((await abstractStaking.getOwedValue(THIRD)) - wei(3, rewardsDecimals)); - - expect(await abstractStaking.getOwedValue(FIRST)).to.equal(wei(1, rewardsDecimals)); - expect(await abstractStaking.getOwedValue(SECOND)).to.equal(wei(2, rewardsDecimals)); - expect(await abstractStaking.getOwedValue(THIRD)).to.equal(wei(3, rewardsDecimals)); - - expect(await abstractStaking.userOwedValue(FIRST)).to.equal(wei(1, rewardsDecimals)); - expect(await abstractStaking.userOwedValue(SECOND)).to.equal(wei(2, rewardsDecimals)); - expect(await abstractStaking.userOwedValue(THIRD)).to.equal(wei(3, rewardsDecimals)); - }); - - it("should allow to claim rewards in several rounds correctly", async () => { - await performStakingManipulations2(); - - await abstractStaking.connect(FIRST).claim((await abstractStaking.getOwedValue(FIRST)) - wei(3)); - - expect(await abstractStaking.getOwedValue(FIRST)).to.equal(wei(3, rewardsDecimals)); - expect(await abstractStaking.userOwedValue(FIRST)).to.equal(wei(3, rewardsDecimals)); - - await abstractStaking.connect(FIRST).claim((await abstractStaking.getOwedValue(FIRST)) - wei(2)); - - expect(await abstractStaking.getOwedValue(FIRST)).to.equal(wei(2, rewardsDecimals)); - expect(await abstractStaking.userOwedValue(FIRST)).to.equal(wei(2, rewardsDecimals)); - - await abstractStaking.connect(FIRST).claim(wei(2, rewardsDecimals)); - - expect(await abstractStaking.getOwedValue(FIRST)).to.equal(0); - expect(await abstractStaking.userOwedValue(FIRST)).to.equal(0); - }); - - it("should transfer tokens correctly on the claim", async () => { - await performStakingManipulations(); - - const initialRewardsBalance = await rewardsToken.balanceOf(abstractStaking); - - const firstOwed = await abstractStaking.getOwedValue(FIRST); - const secondOwed = await abstractStaking.getOwedValue(SECOND); - const thirdOwed = await abstractStaking.getOwedValue(THIRD); - - await abstractStaking.connect(FIRST).claim(firstOwed); - await abstractStaking.connect(SECOND).claim(secondOwed); - - await abstractStaking.connect(THIRD).claim(thirdOwed); - - expect(await rewardsToken.balanceOf(abstractStaking)).to.equal( - initialRewardsBalance - (firstOwed + secondOwed + thirdOwed), - ); - expect(await rewardsToken.balanceOf(FIRST)).to.equal(firstOwed); - expect(await rewardsToken.balanceOf(SECOND)).to.equal(secondOwed); - expect(await rewardsToken.balanceOf(THIRD)).to.equal(thirdOwed); - }); - - it("should not allow to claim 0 rewards", async () => { - await expect(abstractStaking.connect(FIRST).claim(0)).to.be.revertedWith( - "ValueDistributor: amount has to be more than 0", - ); - }); - - it("should not allow to claim more rewards than earned", async () => { - await performStakingManipulations(); - - await expect(abstractStaking.connect(FIRST).claim(wei(4, rewardsDecimals))).to.be.revertedWith( - "ValueDistributor: insufficient amount", - ); - }); - }); - - describe("claimAll()", () => { - it("should claim all the rewards correctly", async () => { - await performStakingManipulations(); - - await abstractStaking.connect(FIRST).claimAll(); - await abstractStaking.connect(SECOND).claimAll(); - await abstractStaking.connect(THIRD).claimAll(); - - expect(await abstractStaking.getOwedValue(FIRST)).to.equal(0); - expect(await abstractStaking.getOwedValue(SECOND)).to.equal(0); - expect(await abstractStaking.getOwedValue(THIRD)).to.equal(0); - - expect(await abstractStaking.userOwedValue(FIRST)).to.equal(0); - expect(await abstractStaking.userOwedValue(SECOND)).to.equal(0); - expect(await abstractStaking.userOwedValue(THIRD)).to.equal(0); - }); - - it("should transfer tokens correctly on the claim", async () => { - await performStakingManipulations(); - - const initialRewardsBalance = await rewardsToken.balanceOf(abstractStaking); - - const firstOwed = await abstractStaking.getOwedValue(FIRST); - const secondOwed = await abstractStaking.getOwedValue(SECOND); - const thirdOwed = await abstractStaking.getOwedValue(THIRD); - - expect(await abstractStaking.connect(FIRST).claimAll.staticCall()).to.eq(firstOwed); - expect(await abstractStaking.connect(SECOND).claimAll.staticCall()).to.eq(secondOwed); - expect(await abstractStaking.connect(THIRD).claimAll.staticCall()).to.eq(thirdOwed); - - await abstractStaking.connect(FIRST).claimAll(); - await abstractStaking.connect(SECOND).claimAll(); - - await abstractStaking.connect(THIRD).claimAll(); - - expect(await rewardsToken.balanceOf(abstractStaking)).to.equal( - initialRewardsBalance - (firstOwed + secondOwed + thirdOwed), - ); - expect(await rewardsToken.balanceOf(FIRST)).to.equal(firstOwed); - expect(await rewardsToken.balanceOf(SECOND)).to.equal(secondOwed); - expect(await rewardsToken.balanceOf(THIRD)).to.equal(thirdOwed); - }); - - it("should not allow to claim 0 rewards", async () => { - await performStakingManipulations(); - - await expect( - abstractStaking.multicall([ - abstractStaking.interface.encodeFunctionData("claimAll"), - abstractStaking.interface.encodeFunctionData("claimAll"), - ]), - ).to.be.revertedWith("ValueDistributor: amount has to be more than 0"); - }); - }); - - describe("rate", () => { - it("should accept 0 as a rate and calculate owed values according to this rate correctly", async () => { - const AbstractStakingMock = await ethers.getContractFactory("AbstractStakingMock"); - abstractStaking = await AbstractStakingMock.deploy(); - - await abstractStaking.__AbstractStakingMock_init(sharesToken, rewardsToken, 0, stakingStartTime); - - await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); - - await abstractStaking.connect(FIRST).stake(wei(100, sharesDecimals)); - - await time.setNextBlockTimestamp((await time.latest()) + 20); - - await abstractStaking.connect(FIRST).unstake(wei(100, sharesDecimals)); - - expect(await abstractStaking.rate()).to.equal(0); - - expect(await abstractStaking.getOwedValue(FIRST)).to.equal(0); - expect(await abstractStaking.userOwedValue(FIRST)).to.equal(0); - expect(await abstractStaking.cumulativeSum()).to.equal(0); - }); - - it("should calculate owed value properly after the rate is changed to 0", async () => { - const AbstractStakingMock = await ethers.getContractFactory("AbstractStakingMock"); - abstractStaking = await AbstractStakingMock.deploy(); - - await abstractStaking.__AbstractStakingMock_init(sharesToken, rewardsToken, rate, stakingStartTime); - - await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); - await mintAndApproveTokens(SECOND, sharesToken, wei(300, sharesDecimals)); - - await abstractStaking.connect(FIRST).stake(wei(50, sharesDecimals)); - await abstractStaking.connect(SECOND).stake(wei(150, sharesDecimals)); - - await abstractStaking.connect(FIRST).unstake(wei(50, sharesDecimals)); - await abstractStaking.connect(SECOND).unstake(wei(100, sharesDecimals)); - - await abstractStaking.setRate(0); - - expect(await abstractStaking.rate()).to.equal(0); - - await abstractStaking.connect(SECOND).unstake(wei(50, sharesDecimals)); - - let firstOwedValue = await abstractStaking.getOwedValue(FIRST); - let secondOwedValue = await abstractStaking.getOwedValue(SECOND); - - await performStakingManipulations(); - - expect(await abstractStaking.userOwedValue(FIRST)).to.equal(firstOwedValue); - expect(await abstractStaking.userOwedValue(SECOND)).to.equal(secondOwedValue); - }); - - it("should work as expected after updating the rate", async () => { - const AbstractStakingMock = await ethers.getContractFactory("AbstractStakingMock"); - abstractStaking = await AbstractStakingMock.deploy(); - - await abstractStaking.__AbstractStakingMock_init(sharesToken, rewardsToken, rate, stakingStartTime); - - await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); - await mintAndApproveTokens(SECOND, sharesToken, wei(300, sharesDecimals)); - - await abstractStaking.connect(FIRST).stake(wei(50, sharesDecimals)); - await abstractStaking.connect(SECOND).stake(wei(150, sharesDecimals)); - - await abstractStaking.connect(FIRST).unstake(wei(50, sharesDecimals)); - await abstractStaking.connect(SECOND).unstake(wei(100, sharesDecimals)); - - const prevCumulativeSum = await abstractStaking.cumulativeSum(); - - let firstOwedValue = await abstractStaking.getOwedValue(FIRST); - let secondOwedValue = await abstractStaking.getOwedValue(SECOND); - - await abstractStaking.setRate(wei(2, rewardsDecimals)); - - const expectedCumulativeSum = prevCumulativeSum + wei(rate, 25) / (await abstractStaking.totalShares()); - - expect(await abstractStaking.rate()).to.equal(wei(2, rewardsDecimals)); - expect(await abstractStaking.cumulativeSum()).to.equal(expectedCumulativeSum); - expect(await abstractStaking.updatedAt()).to.equal(await time.latest()); - - expect(await abstractStaking.userOwedValue(FIRST)).to.equal(firstOwedValue); - expect(await abstractStaking.userOwedValue(SECOND)).to.equal(secondOwedValue); - }); - }); - - describe("should handle staking manipulations with 6-decimal values", () => { - it("should handle the whole staling process using 6-decimal values", async () => { - const AbstractStakingMock = await ethers.getContractFactory("AbstractStakingMock"); - const ERC20Mock = await ethers.getContractFactory("ERC20Mock"); - - abstractStaking = await AbstractStakingMock.deploy(); - sharesToken = await ERC20Mock.deploy("SharesMock", "SMock", 6); - rewardsToken = await ERC20Mock.deploy("RewardsMock", "RMock", 6); - - sharesDecimals = Number(await sharesToken.decimals()); - rewardsDecimals = Number(await rewardsToken.decimals()); - - await rewardsToken.mint(await abstractStaking.getAddress(), wei(100, rewardsDecimals)); - - await abstractStaking.__AbstractStakingMock_init(sharesToken, rewardsToken, wei(1, rewardsDecimals), 3n); - - await performStakingManipulations2(); - - await checkManipulationRewards2(); - }); - }); -}); diff --git a/test/finance/staking/AbstractValueDistributor.test.ts b/test/finance/staking/AbstractValueDistributor.test.ts deleted file mode 100644 index a20e8593..00000000 --- a/test/finance/staking/AbstractValueDistributor.test.ts +++ /dev/null @@ -1,365 +0,0 @@ -import { ethers } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; -import { time } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; -import { Reverter } from "@/test/helpers/reverter"; -import { wei } from "@/scripts/utils/utils"; - -import { AbstractValueDistributorMock } from "@ethers-v6"; -import { ZERO_ADDR } from "@/scripts/utils/constants"; - -describe("AbstractValueDistributor", () => { - const reverter = new Reverter(); - - let FIRST: SignerWithAddress; - let SECOND: SignerWithAddress; - let THIRD: SignerWithAddress; - let FOURTH: SignerWithAddress; - - let abstractValueDistributor: AbstractValueDistributorMock; - - const addSharesToAllUsers = async (shares: Array) => { - await abstractValueDistributor.addShares(FIRST, shares[0]); - await abstractValueDistributor.addShares(SECOND, shares[1]); - await abstractValueDistributor.addShares(THIRD, shares[2]); - await abstractValueDistributor.addShares(FOURTH, shares[3]); - }; - - const removeSharesFromAllUsers = async (shares: Array) => { - await abstractValueDistributor.removeShares(FIRST, shares[0]); - await abstractValueDistributor.removeShares(SECOND, shares[1]); - await abstractValueDistributor.removeShares(THIRD, shares[2]); - await abstractValueDistributor.removeShares(FOURTH, shares[3]); - }; - - const checkAllShares = async (shares: Array) => { - expect(await abstractValueDistributor.userShares(FIRST)).to.equal(shares[0]); - expect(await abstractValueDistributor.userShares(SECOND)).to.equal(shares[1]); - expect(await abstractValueDistributor.userShares(THIRD)).to.equal(shares[2]); - expect(await abstractValueDistributor.userShares(FOURTH)).to.equal(shares[3]); - }; - - const performSharesManipulations = async () => { - await time.setNextBlockTimestamp((await time.latest()) + 2); - - await abstractValueDistributor.addShares(FIRST, 100); - - await time.setNextBlockTimestamp((await time.latest()) + 2); - - await abstractValueDistributor.addShares(SECOND, 200); - - await abstractValueDistributor.addShares(THIRD, 100); - - await time.setNextBlockTimestamp((await time.latest()) + 3); - - await abstractValueDistributor.removeShares(FIRST, 100); - - await abstractValueDistributor.addShares(THIRD, 100); - - await abstractValueDistributor.removeShares(SECOND, 200); - - await time.setNextBlockTimestamp((await time.latest()) + 2); - - await abstractValueDistributor.removeShares(THIRD, 200); - }; - - before("setup", async () => { - [FIRST, SECOND, THIRD, FOURTH] = await ethers.getSigners(); - - const abstractValueDistributorMock = await ethers.getContractFactory("AbstractValueDistributorMock"); - abstractValueDistributor = await abstractValueDistributorMock.deploy(); - - await reverter.snapshot(); - }); - - afterEach(reverter.revert); - - describe("addShares()", () => { - it("should add shares correctly", async () => { - await addSharesToAllUsers([1, 2, 3, 4]); - await abstractValueDistributor.addShares(FIRST, 5); - - await checkAllShares([6, 2, 3, 4]); - expect(await abstractValueDistributor.totalShares()).to.equal(15); - }); - - it("should not allow to add 0 shares", async () => { - await expect(abstractValueDistributor.addShares(SECOND, 0)).to.be.revertedWith( - "ValueDistributor: amount has to be more than 0", - ); - }); - - it("should not allow zero address to add shares", async () => { - await expect(abstractValueDistributor.addShares(ZERO_ADDR, 2)).to.be.revertedWith( - "ValueDistributor: zero address is not allowed", - ); - }); - }); - - describe("removeShares()", () => { - it("should correctly remove shares partially", async () => { - await addSharesToAllUsers([3, 2, 3, 4]); - - await removeSharesFromAllUsers([1, 1, 1, 2]); - await abstractValueDistributor.removeShares(THIRD, 1); - - await checkAllShares([2, 1, 1, 2]); - expect(await abstractValueDistributor.totalShares()).to.equal(6); - }); - - it("should handle removing all the shares correctly", async () => { - await addSharesToAllUsers([2, 1, 1, 2]); - - await removeSharesFromAllUsers([2, 1, 1, 2]); - - await checkAllShares([0, 0, 0, 0]); - - expect(await abstractValueDistributor.totalShares()).to.equal(0); - - const cumulativeSum = await abstractValueDistributor.cumulativeSum(); - - await abstractValueDistributor.addShares(FIRST, 2); - - expect(await abstractValueDistributor.cumulativeSum()).to.equal(cumulativeSum); - expect(await abstractValueDistributor.totalShares()).to.equal(2); - expect(await abstractValueDistributor.userShares(FIRST)).to.equal(2); - }); - - it("should not allow to remove 0 shares", async () => { - await expect(abstractValueDistributor.removeShares(SECOND, 0)).to.be.revertedWith( - "ValueDistributor: amount has to be more than 0", - ); - }); - - it("should not allow zero address to remove shares", async () => { - await expect(abstractValueDistributor.removeShares(ZERO_ADDR, 2)).to.be.revertedWith( - "ValueDistributor: insufficient amount", - ); - }); - - it("should not allow to remove more shares than it was added", async () => { - await expect(abstractValueDistributor.removeShares(SECOND, 1)).to.be.revertedWith( - "ValueDistributor: insufficient amount", - ); - }); - }); - - describe("distributeValue()", () => { - it("should calculate the value owed to a user correctly", async () => { - await abstractValueDistributor.addShares(FIRST, 100); - - await time.setNextBlockTimestamp((await time.latest()) + 30); - - await abstractValueDistributor.removeShares(FIRST, 100); - - expect(await abstractValueDistributor.getOwedValue(FIRST)).to.equal(wei(30)); - expect(await abstractValueDistributor.userOwedValue(FIRST)).to.equal(wei(30)); - }); - - it("should calculate the value owed to multiple users correctly", async () => { - await performSharesManipulations(); - - const firstExpectedReward = wei(3) + wei(1) / 12n; - const secondExpectedReward = wei(3) + wei(1) / 3n; - const thirdExpectedReward = wei(3) + wei(7) / 12n; - - expect(await abstractValueDistributor.getOwedValue(FIRST)).to.equal(firstExpectedReward); - expect(await abstractValueDistributor.getOwedValue(SECOND)).to.equal(secondExpectedReward); - expect(await abstractValueDistributor.getOwedValue(THIRD)).to.equal(thirdExpectedReward); - - expect(await abstractValueDistributor.userOwedValue(FIRST)).to.equal(firstExpectedReward); - expect(await abstractValueDistributor.userOwedValue(SECOND)).to.equal(secondExpectedReward); - expect(await abstractValueDistributor.userOwedValue(THIRD)).to.equal(thirdExpectedReward); - }); - - it("should calculate the value owed to multiple users correctly", async () => { - await abstractValueDistributor.addShares(FIRST, 200); - - await time.setNextBlockTimestamp((await time.latest()) + 3); - - await abstractValueDistributor.addShares(SECOND, 100); - - await abstractValueDistributor.addShares(THIRD, 300); - - await time.setNextBlockTimestamp((await time.latest()) + 3); - - await abstractValueDistributor.addShares(FIRST, 200); - - await abstractValueDistributor.removeShares(FIRST, 100); - - await time.setNextBlockTimestamp((await time.latest()) + 3); - - await abstractValueDistributor.removeShares(SECOND, 100); - - await abstractValueDistributor.addShares(THIRD, 100); - - await time.setNextBlockTimestamp((await time.latest()) + 2); - - await abstractValueDistributor.removeShares(FIRST, 300); - - await abstractValueDistributor.removeShares(THIRD, 400); - - const firstExpectedReward = wei(7) + wei(2) / 3n + wei(1) / 7n; - const secondExpectedReward = wei(233) / 168n; - const thirdExpectedReward = wei(5) + wei(3) / 8n + wei(3) / 7n; - - expect(await abstractValueDistributor.getOwedValue(FIRST)).to.equal(firstExpectedReward); - expect(await abstractValueDistributor.getOwedValue(SECOND)).to.equal(secondExpectedReward); - expect(await abstractValueDistributor.getOwedValue(THIRD)).to.equal(thirdExpectedReward); - - expect(await abstractValueDistributor.userOwedValue(FIRST)).to.equal(firstExpectedReward); - expect(await abstractValueDistributor.userOwedValue(SECOND)).to.equal(secondExpectedReward); - expect(await abstractValueDistributor.userOwedValue(THIRD)).to.equal(thirdExpectedReward); - }); - - it("should distribute all the owed values correctly", async () => { - await performSharesManipulations(); - - await abstractValueDistributor.distributeValue(FIRST, await abstractValueDistributor.getOwedValue(FIRST)); - await abstractValueDistributor.distributeValue(SECOND, await abstractValueDistributor.getOwedValue(SECOND)); - await abstractValueDistributor.distributeValue(THIRD, await abstractValueDistributor.getOwedValue(THIRD)); - - expect(await abstractValueDistributor.getOwedValue(FIRST)).to.equal(0); - expect(await abstractValueDistributor.getOwedValue(SECOND)).to.equal(0); - expect(await abstractValueDistributor.getOwedValue(THIRD)).to.equal(0); - - expect(await abstractValueDistributor.userOwedValue(FIRST)).to.equal(0); - expect(await abstractValueDistributor.userOwedValue(SECOND)).to.equal(0); - expect(await abstractValueDistributor.userOwedValue(THIRD)).to.equal(0); - }); - - it("should distribute all the owed values optimally", async () => { - await performSharesManipulations(); - - const firstOwed = await abstractValueDistributor.getOwedValue(FIRST); - const secondOwed = await abstractValueDistributor.getOwedValue(SECOND); - const thirdOwed = await abstractValueDistributor.getOwedValue(THIRD); - - expect(await abstractValueDistributor.distributeAllValue.staticCall(FIRST)).to.eq(firstOwed); - expect(await abstractValueDistributor.distributeAllValue.staticCall(SECOND)).to.eq(secondOwed); - expect(await abstractValueDistributor.distributeAllValue.staticCall(THIRD)).to.eq(thirdOwed); - - await abstractValueDistributor.distributeAllValue(FIRST); - await abstractValueDistributor.distributeAllValue(SECOND); - await abstractValueDistributor.distributeAllValue(THIRD); - - expect(await abstractValueDistributor.getOwedValue(FIRST)).to.equal(0); - expect(await abstractValueDistributor.getOwedValue(SECOND)).to.equal(0); - expect(await abstractValueDistributor.getOwedValue(THIRD)).to.equal(0); - - expect(await abstractValueDistributor.userOwedValue(FIRST)).to.equal(0); - expect(await abstractValueDistributor.userOwedValue(SECOND)).to.equal(0); - expect(await abstractValueDistributor.userOwedValue(THIRD)).to.equal(0); - }); - - it("should correctly distribute owed values partially", async () => { - await performSharesManipulations(); - - await abstractValueDistributor.distributeValue( - FIRST, - (await abstractValueDistributor.getOwedValue(FIRST)) - wei(1), - ); - await abstractValueDistributor.distributeValue( - SECOND, - (await abstractValueDistributor.getOwedValue(SECOND)) - wei(2), - ); - await abstractValueDistributor.distributeValue( - THIRD, - (await abstractValueDistributor.getOwedValue(THIRD)) - wei(3), - ); - - expect(await abstractValueDistributor.getOwedValue(FIRST)).to.equal(wei(1)); - expect(await abstractValueDistributor.getOwedValue(SECOND)).to.equal(wei(2)); - expect(await abstractValueDistributor.getOwedValue(THIRD)).to.equal(wei(3)); - - expect(await abstractValueDistributor.userOwedValue(FIRST)).to.equal(wei(1)); - expect(await abstractValueDistributor.userOwedValue(SECOND)).to.equal(wei(2)); - expect(await abstractValueDistributor.userOwedValue(THIRD)).to.equal(wei(3)); - }); - - it("should allow to distribute values in several rounds correctly", async () => { - await performSharesManipulations(); - - await abstractValueDistributor.distributeValue( - FIRST, - (await abstractValueDistributor.getOwedValue(FIRST)) - wei(3), - ); - - expect(await abstractValueDistributor.getOwedValue(FIRST)).to.equal(wei(3)); - expect(await abstractValueDistributor.userOwedValue(FIRST)).to.equal(wei(3)); - - await abstractValueDistributor.distributeValue( - FIRST, - (await abstractValueDistributor.getOwedValue(FIRST)) - wei(2), - ); - - expect(await abstractValueDistributor.getOwedValue(FIRST)).to.equal(wei(2)); - expect(await abstractValueDistributor.userOwedValue(FIRST)).to.equal(wei(2)); - - await abstractValueDistributor.distributeValue(FIRST, wei(2)); - - expect(await abstractValueDistributor.getOwedValue(FIRST)).to.equal(0); - expect(await abstractValueDistributor.userOwedValue(FIRST)).to.equal(wei(0)); - }); - - it("should not allow to distribute 0 values", async () => { - await expect(abstractValueDistributor.distributeValue(FIRST, 0)).to.be.revertedWith( - "ValueDistributor: amount has to be more than 0", - ); - }); - - it("should not allow zero address to distribute values", async () => { - await expect(abstractValueDistributor.distributeValue(ZERO_ADDR, 2)).to.be.revertedWith( - "ValueDistributor: insufficient amount", - ); - }); - - it("should not allow to distribute more values than owed", async () => { - await performSharesManipulations(); - - await expect(abstractValueDistributor.distributeValue(FIRST, wei(4))).to.be.revertedWith( - "ValueDistributor: insufficient amount", - ); - }); - }); - - describe("same block transactions", () => { - it("should work as expected if more than one transaction which updates the key values is sent within one block", async () => { - await abstractValueDistributor.addShares(FIRST, 100); - - await abstractValueDistributor.multicall([ - abstractValueDistributor.interface.encodeFunctionData("addShares", [await FIRST.getAddress(), 200]), - abstractValueDistributor.interface.encodeFunctionData("addShares", [await SECOND.getAddress(), 200]), - ]); - - await time.setNextBlockTimestamp((await time.latest()) + 3); - - await abstractValueDistributor.removeShares(SECOND, 100); - - await time.setNextBlockTimestamp((await time.latest()) + 4); - - await abstractValueDistributor.addShares(THIRD, 100); - - await abstractValueDistributor.multicall([ - abstractValueDistributor.interface.encodeFunctionData("removeShares", [await FIRST.getAddress(), 300]), - abstractValueDistributor.interface.encodeFunctionData("removeShares", [await SECOND.getAddress(), 100]), - ]); - - await time.setNextBlockTimestamp((await time.latest()) + 3); - - await abstractValueDistributor.removeShares(THIRD, 100); - - const firstExpectedReward = wei(6) + wei(2) / 5n; - const secondExpectedReward = wei(2) + wei(2) / 5n; - const thirdExpectedReward = wei(3) + wei(1) / 5n; - - expect(await abstractValueDistributor.getOwedValue(FIRST)).to.equal(firstExpectedReward); - expect(await abstractValueDistributor.getOwedValue(SECOND)).to.equal(secondExpectedReward); - expect(await abstractValueDistributor.getOwedValue(THIRD)).to.equal(thirdExpectedReward); - - expect(await abstractValueDistributor.userOwedValue(FIRST)).to.equal(firstExpectedReward); - expect(await abstractValueDistributor.userOwedValue(SECOND)).to.equal(secondExpectedReward); - expect(await abstractValueDistributor.userOwedValue(THIRD)).to.equal(thirdExpectedReward); - }); - }); -}); diff --git a/test/finance/staking/Staking.test.ts b/test/finance/staking/Staking.test.ts new file mode 100644 index 00000000..1a750cb0 --- /dev/null +++ b/test/finance/staking/Staking.test.ts @@ -0,0 +1,769 @@ +import { ethers } from "hardhat"; +import { expect } from "chai"; + +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; +import { time } from "@nomicfoundation/hardhat-network-helpers"; + +import { Reverter } from "@/test/helpers/reverter"; + +import { StakingMock, ERC20Mock } from "@ethers-v6"; +import { wei } from "@/scripts/utils/utils"; + +describe("Staking", () => { + const reverter = new Reverter(); + + let FIRST: SignerWithAddress; + let SECOND: SignerWithAddress; + let THIRD: SignerWithAddress; + + let sharesToken: ERC20Mock; + let rewardsToken: ERC20Mock; + + let sharesDecimals: number; + let rewardsDecimals: number; + + let stakingStartTime: bigint; + let rate: bigint; + + let staking: StakingMock; + + const mintAndApproveTokens = async (user: SignerWithAddress, token: ERC20Mock, amount: bigint) => { + await token.mint(user, amount); + await token.connect(user).approve(staking, amount); + }; + + const performStakingManipulations = async () => { + await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); + await mintAndApproveTokens(SECOND, sharesToken, wei(200, sharesDecimals)); + await mintAndApproveTokens(THIRD, sharesToken, wei(200, sharesDecimals)); + + await time.setNextBlockTimestamp((await time.latest()) + 2); + + await staking.connect(FIRST).stake(wei(100, sharesDecimals)); + + await time.setNextBlockTimestamp((await time.latest()) + 2); + + await staking.connect(SECOND).stake(wei(200, sharesDecimals)); + + await staking.connect(THIRD).stake(wei(100, sharesDecimals)); + + await time.setNextBlockTimestamp((await time.latest()) + 3); + + await staking.connect(FIRST).unstake(wei(100, sharesDecimals)); + + await staking.connect(THIRD).stake(wei(100, sharesDecimals)); + + await staking.connect(SECOND).unstake(wei(200, sharesDecimals)); + + await time.setNextBlockTimestamp((await time.latest()) + 2); + + await staking.connect(THIRD).unstake(wei(200, sharesDecimals)); + }; + + const checkManipulationRewards = async () => { + const firstExpectedReward = wei(3, rewardsDecimals) + wei(1, rewardsDecimals) / 12n; + const secondExpectedReward = wei(3, rewardsDecimals) + wei(1, rewardsDecimals) / 3n; + const thirdExpectedReward = wei(3, rewardsDecimals) + wei(7, rewardsDecimals) / 12n; + + expect(await staking.getOwedValue(FIRST)).to.equal(firstExpectedReward); + expect(await staking.getOwedValue(SECOND)).to.equal(secondExpectedReward); + expect(await staking.getOwedValue(THIRD)).to.equal(thirdExpectedReward); + + expect(await staking.userOwedValue(FIRST)).to.equal(firstExpectedReward); + expect(await staking.userOwedValue(SECOND)).to.equal(secondExpectedReward); + expect(await staking.userOwedValue(THIRD)).to.equal(thirdExpectedReward); + }; + + const performStakingManipulations2 = async () => { + await mintAndApproveTokens(FIRST, sharesToken, wei(400, sharesDecimals)); + await mintAndApproveTokens(SECOND, sharesToken, wei(100, sharesDecimals)); + await mintAndApproveTokens(THIRD, sharesToken, wei(400, sharesDecimals)); + + await staking.connect(FIRST).stake(wei(200, sharesDecimals)); + + await time.setNextBlockTimestamp((await time.latest()) + 3); + + await staking.connect(SECOND).stake(wei(100, sharesDecimals)); + + await staking.connect(THIRD).stake(wei(300, sharesDecimals)); + + await time.setNextBlockTimestamp((await time.latest()) + 3); + + await staking.connect(FIRST).stake(wei(200, sharesDecimals)); + + await staking.connect(FIRST).unstake(wei(100, sharesDecimals)); + + await time.setNextBlockTimestamp((await time.latest()) + 3); + + await staking.connect(SECOND).unstake(wei(100, sharesDecimals)); + + await staking.connect(THIRD).stake(wei(100, sharesDecimals)); + + await time.setNextBlockTimestamp((await time.latest()) + 2); + + await staking.connect(FIRST).unstake(wei(300, sharesDecimals)); + + await staking.connect(THIRD).unstake(wei(400, sharesDecimals)); + }; + + const checkManipulationRewards2 = async () => { + const firstExpectedReward = wei(7, rewardsDecimals) + wei(2, rewardsDecimals) / 3n + wei(1, rewardsDecimals) / 7n; + const secondExpectedReward = wei(233, rewardsDecimals) / 168n; + const thirdExpectedReward = wei(5, rewardsDecimals) + wei(3, rewardsDecimals) / 8n + wei(3, rewardsDecimals) / 7n; + + expect(await staking.getOwedValue(FIRST)).to.equal(firstExpectedReward); + expect(await staking.getOwedValue(SECOND)).to.equal(secondExpectedReward); + expect(await staking.getOwedValue(THIRD)).to.equal(thirdExpectedReward); + + expect(await staking.userOwedValue(FIRST)).to.equal(firstExpectedReward); + expect(await staking.userOwedValue(SECOND)).to.equal(secondExpectedReward); + expect(await staking.userOwedValue(THIRD)).to.equal(thirdExpectedReward); + }; + + before("setup", async () => { + [FIRST, SECOND, THIRD] = await ethers.getSigners(); + + const StakingMock = await ethers.getContractFactory("StakingMock"); + const ERC20Mock = await ethers.getContractFactory("ERC20Mock"); + + staking = await StakingMock.deploy(); + sharesToken = await ERC20Mock.deploy("SharesMock", "SMock", 18); + rewardsToken = await ERC20Mock.deploy("RewardsMock", "RMock", 18); + + sharesDecimals = Number(await sharesToken.decimals()); + rewardsDecimals = Number(await rewardsToken.decimals()); + + await rewardsToken.mint(await staking.getAddress(), wei(100, rewardsDecimals)); + + stakingStartTime = 3n; + rate = wei(1, rewardsDecimals); + + await staking.__StakingMock_init(sharesToken, rewardsToken, rate, stakingStartTime); + + await reverter.snapshot(); + }); + + afterEach(reverter.revert); + + describe("AStaking initialization", () => { + it("should not initialize twice", async () => { + await expect(staking.mockInit(sharesToken, rewardsToken, rate, stakingStartTime)) + .to.be.revertedWithCustomError(staking, "NotInitializing") + .withArgs(); + + await expect(staking.__StakingMock_init(sharesToken, rewardsToken, rate, stakingStartTime)) + .to.be.revertedWithCustomError(staking, "InvalidInitialization") + .withArgs(); + }); + + it("should set the initial values correctly", async () => { + expect(await staking.sharesToken()).to.equal(await sharesToken.getAddress()); + expect(await staking.rewardsToken()).to.equal(await rewardsToken.getAddress()); + expect(await staking.rate()).to.equal(rate); + expect(await staking.stakingStartTime()).to.equal(stakingStartTime); + }); + + it("should not allow to set 0 as a Shares Token or Rewards Token", async () => { + const StakingMock = await ethers.getContractFactory("StakingMock"); + let staking = await StakingMock.deploy(); + + await expect(staking.__StakingMock_init(ethers.ZeroAddress, rewardsToken, rate, stakingStartTime)) + .to.be.revertedWithCustomError(staking, "SharesTokenIsZeroAddress") + .withArgs(); + + await expect(staking.__StakingMock_init(sharesToken, ethers.ZeroAddress, rate, stakingStartTime)) + .to.be.revertedWithCustomError(staking, "RewardsTokenIsZeroAddress") + .withArgs(); + }); + }); + + describe("timestamps", () => { + it("should not allow to stake, unstake, withdraw tokens or claim rewards before the start of the staking", async () => { + const stakingStartTime = 1638474321; + await staking.setStakingStartTime(stakingStartTime); + + await expect(staking.stake(wei(100, sharesDecimals))) + .to.be.revertedWithCustomError(staking, "StakingHasNotStarted") + .withArgs((await time.latest()) + 1, stakingStartTime); + await expect(staking.unstake(wei(100, sharesDecimals))) + .to.be.revertedWithCustomError(staking, "StakingHasNotStarted") + .withArgs((await time.latest()) + 1, stakingStartTime); + await expect(staking.withdraw()) + .to.be.revertedWithCustomError(staking, "StakingHasNotStarted") + .withArgs((await time.latest()) + 1, stakingStartTime); + await expect(staking.claim(wei(100, sharesDecimals))) + .to.be.revertedWithCustomError(staking, "StakingHasNotStarted") + .withArgs((await time.latest()) + 1, stakingStartTime); + await expect(staking.claimAll()) + .to.be.revertedWithCustomError(staking, "StakingHasNotStarted") + .withArgs((await time.latest()) + 1, stakingStartTime); + }); + + it("should work as expected if the staking start time is set to the timestamp in the past", async () => { + await time.setNextBlockTimestamp((await time.latest()) + 20); + + await staking.setStakingStartTime(2); + + expect(await staking.stakingStartTime()).to.equal(2); + + await performStakingManipulations(); + + await checkManipulationRewards(); + }); + + it("should update values correctly if more than one transaction which updates the key values is sent within one block", async () => { + await mintAndApproveTokens(FIRST, sharesToken, wei(400, sharesDecimals)); + + await staking.connect(FIRST).stake(wei(100, sharesDecimals)); + await staking.connect(FIRST).stake(wei(100, sharesDecimals)); + + await staking.multicall([ + staking.interface.encodeFunctionData("stake", [wei(100, sharesDecimals)]), + staking.interface.encodeFunctionData("stake", [wei(100, sharesDecimals)]), + ]); + + expect(await staking.userShares(FIRST)).to.equal(wei(400, sharesDecimals)); + expect(await staking.cumulativeSum()).to.equal(wei(1.5, 23)); + }); + + it("should work as expected if more than one transaction which updates the key values is sent within one block", async () => { + const StakersFactory = await ethers.getContractFactory("StakersFactory"); + const stakersFactory = await StakersFactory.deploy(); + + await stakersFactory.createStaker(); + await stakersFactory.createStaker(); + + const staker1 = await stakersFactory.stakers(0); + const staker2 = await stakersFactory.stakers(1); + + await sharesToken.mint(staker1, wei(500, sharesDecimals)); + await sharesToken.mint(staker2, wei(500, sharesDecimals)); + await mintAndApproveTokens(THIRD, sharesToken, wei(100, sharesDecimals)); + + await stakersFactory.stake(staking, staker1, sharesToken, wei(100, sharesDecimals)); + + await stakersFactory.multicall([ + stakersFactory.interface.encodeFunctionData("stake", [ + await staking.getAddress(), + staker1, + await sharesToken.getAddress(), + wei(200, sharesDecimals), + ]), + stakersFactory.interface.encodeFunctionData("stake", [ + await staking.getAddress(), + staker2, + await sharesToken.getAddress(), + wei(200, sharesDecimals), + ]), + ]); + + await time.setNextBlockTimestamp((await time.latest()) + 3); + + await stakersFactory.unstake(staking, staker2, wei(100, sharesDecimals)); + + await time.setNextBlockTimestamp((await time.latest()) + 4); + + await staking.connect(THIRD).stake(wei(100, sharesDecimals)); + + await stakersFactory.multicall([ + stakersFactory.interface.encodeFunctionData("unstake", [ + await staking.getAddress(), + staker1, + wei(300, sharesDecimals), + ]), + stakersFactory.interface.encodeFunctionData("unstake", [ + await staking.getAddress(), + staker2, + wei(100, sharesDecimals), + ]), + ]); + + await time.setNextBlockTimestamp((await time.latest()) + 3); + + await staking.connect(THIRD).unstake(wei(100, sharesDecimals)); + + const firstExpectedReward = wei(6, rewardsDecimals) + wei(2, rewardsDecimals) / 5n; + const secondExpectedReward = wei(2, rewardsDecimals) + wei(2, rewardsDecimals) / 5n; + const thirdExpectedReward = wei(3, rewardsDecimals) + wei(1, rewardsDecimals) / 5n; + + expect(await staking.getOwedValue(staker1)).to.equal(firstExpectedReward); + expect(await staking.getOwedValue(staker2)).to.equal(secondExpectedReward); + expect(await staking.getOwedValue(THIRD)).to.equal(thirdExpectedReward); + + expect(await staking.userOwedValue(staker1)).to.equal(firstExpectedReward); + expect(await staking.userOwedValue(staker2)).to.equal(secondExpectedReward); + expect(await staking.userOwedValue(THIRD)).to.equal(thirdExpectedReward); + }); + }); + + describe("stake()", () => { + it("should add shares after staking correctly", async () => { + await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); + await mintAndApproveTokens(SECOND, sharesToken, wei(300, sharesDecimals)); + + await staking.connect(FIRST).stake(wei(100, sharesDecimals)); + await staking.connect(SECOND).stake(wei(300, sharesDecimals)); + + expect(await staking.totalShares()).to.equal(wei(400, sharesDecimals)); + expect(await staking.userShares(FIRST)).to.equal(wei(100, sharesDecimals)); + expect(await staking.userShares(SECOND)).to.equal(wei(300, sharesDecimals)); + }); + + it("should transfer tokens correctly on stake", async () => { + await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); + + await staking.connect(FIRST).stake(wei(50, sharesDecimals)); + + expect(await sharesToken.balanceOf(FIRST)).to.equal(wei(50, sharesDecimals)); + expect(await sharesToken.balanceOf(staking)).to.equal(wei(50, sharesDecimals)); + + await staking.connect(FIRST).stake(wei(50, sharesDecimals)); + + expect(await sharesToken.balanceOf(FIRST)).to.equal(0); + expect(await sharesToken.balanceOf(staking)).to.equal(wei(100, sharesDecimals)); + }); + + it("should not allow to stake 0 tokens", async () => { + await expect(staking.connect(FIRST).stake(0)).to.be.revertedWithCustomError(staking, "AmountIsZero").withArgs(); + }); + }); + + describe("unstake()", () => { + it("should remove shares after unstaking correctly", async () => { + await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); + await mintAndApproveTokens(SECOND, sharesToken, wei(300, sharesDecimals)); + + await staking.connect(FIRST).stake(wei(100, sharesDecimals)); + await staking.connect(SECOND).stake(wei(300, sharesDecimals)); + + await staking.connect(FIRST).unstake(wei(50, sharesDecimals)); + await staking.connect(SECOND).unstake(wei(200, sharesDecimals)); + + expect(await staking.totalShares()).to.equal(wei(150, sharesDecimals)); + expect(await staking.userShares(FIRST)).to.equal(wei(50, sharesDecimals)); + expect(await staking.userShares(SECOND)).to.equal(wei(100, sharesDecimals)); + }); + + it("should handle unstaking the whole amount staked correctly", async () => { + await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); + await mintAndApproveTokens(SECOND, sharesToken, wei(200, sharesDecimals)); + + await staking.connect(FIRST).stake(wei(100, sharesDecimals)); + await staking.connect(SECOND).stake(wei(200, sharesDecimals)); + + await staking.connect(FIRST).unstake(wei(100, sharesDecimals)); + await staking.connect(SECOND).unstake(wei(200, sharesDecimals)); + + const cumulativeSum = await staking.cumulativeSum(); + + expect(await staking.totalShares()).to.equal(0); + expect(await staking.userShares(FIRST)).to.equal(0); + expect(await staking.userShares(SECOND)).to.equal(0); + + await sharesToken.connect(FIRST).approve(staking, wei(50, sharesDecimals)); + await sharesToken.connect(SECOND).approve(staking, wei(100, sharesDecimals)); + + await staking.connect(FIRST).stake(wei(50, sharesDecimals)); + + expect(await staking.cumulativeSum()).to.equal(cumulativeSum); + + await staking.connect(SECOND).stake(wei(100, sharesDecimals)); + + expect(await staking.totalShares()).to.equal(wei(150, sharesDecimals)); + expect(await staking.userShares(FIRST)).to.equal(wei(50, sharesDecimals)); + expect(await staking.userShares(SECOND)).to.equal(wei(100, sharesDecimals)); + }); + + it("should transfer tokens correctly on unstake", async () => { + await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); + + await staking.connect(FIRST).stake(wei(100, sharesDecimals)); + + await staking.connect(FIRST).unstake(wei(50, sharesDecimals)); + + expect(await sharesToken.balanceOf(FIRST)).to.equal(wei(50, sharesDecimals)); + expect(await sharesToken.balanceOf(staking)).to.equal(wei(50, sharesDecimals)); + + await staking.connect(FIRST).unstake(wei(50, sharesDecimals)); + + expect(await sharesToken.balanceOf(FIRST)).to.equal(wei(100, sharesDecimals)); + expect(await sharesToken.balanceOf(staking)).to.equal(0); + }); + + it("should not allow to unstake 0 tokens", async () => { + await expect(staking.connect(FIRST).unstake(0)).to.be.revertedWithCustomError(staking, "AmountIsZero").withArgs(); + }); + + it("should not allow to unstake more than it was staked", async () => { + await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); + + await staking.connect(FIRST).stake(wei(100, sharesDecimals)); + await expect(staking.connect(FIRST).unstake(wei(150, sharesDecimals))) + .to.be.revertedWithCustomError(staking, "InsufficientSharesAmount") + .withArgs(FIRST.address, wei(100, sharesDecimals), wei(150, sharesDecimals)); + }); + }); + + describe("withdraw()", () => { + it("should withdraw tokens correctly", async () => { + await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); + + await staking.connect(FIRST).stake(wei(100, sharesDecimals)); + + await staking.connect(FIRST).withdraw(); + + expect(await staking.totalShares()).to.equal(0); + expect(await staking.userShares(FIRST)).to.equal(0); + }); + + it("should transfer tokens correctly on withdraw", async () => { + await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); + + await staking.connect(FIRST).stake(wei(100, sharesDecimals)); + + await staking.connect(FIRST).withdraw(); + + expect(await sharesToken.balanceOf(staking)).to.equal(0); + expect(await sharesToken.balanceOf(FIRST)).to.equal(wei(100, sharesDecimals)); + }); + + it("should claim all the rewards earned after the withdrawal", async () => { + await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); + + await staking.connect(FIRST).stake(wei(100, sharesDecimals)); + + await time.setNextBlockTimestamp((await time.latest()) + 30); + + await staking.connect(FIRST).withdraw(); + + expect(await staking.getOwedValue(FIRST)).to.equal(0); + expect(await staking.userOwedValue(FIRST)).to.equal(0); + }); + + it("should work as expected if withdraw is called right after claiming all the rewards within one block", async () => { + await mintAndApproveTokens(SECOND, sharesToken, wei(200, sharesDecimals)); + await mintAndApproveTokens(FIRST, sharesToken, wei(200, sharesDecimals)); + + await staking.connect(FIRST).stake(wei(100, sharesDecimals)); + + // triggering the next block + await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); + + await staking.multicall([ + staking.interface.encodeFunctionData("claim", [await staking.getOwedValue(FIRST)]), + staking.interface.encodeFunctionData("withdraw"), + ]); + + expect(await staking.getOwedValue(FIRST)).to.equal(0); + expect(await staking.userOwedValue(FIRST)).to.equal(0); + expect(await staking.userShares(FIRST)).to.equal(0); + }); + + it("should withdraw as expected if there are no rewards because of the 0 rate", async () => { + await staking.setRate(0); + + await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); + + await staking.connect(FIRST).stake(wei(100, sharesDecimals)); + + await time.setNextBlockTimestamp((await time.latest()) + 30); + + await staking.withdraw(); + + expect(await staking.getOwedValue(FIRST)).to.equal(0); + expect(await staking.userOwedValue(FIRST)).to.equal(0); + expect(await staking.userShares(FIRST)).to.equal(0); + }); + + it("should not allow to withdraw if there are no shares", async () => { + await mintAndApproveTokens(FIRST, sharesToken, wei(200, sharesDecimals)); + + await staking.connect(FIRST).stake(wei(200, sharesDecimals)); + + await expect( + staking.multicall([ + staking.interface.encodeFunctionData("unstake", [wei(200, sharesDecimals)]), + staking.interface.encodeFunctionData("withdraw"), + ]), + ) + .to.be.revertedWithCustomError(staking, "AmountIsZero") + .withArgs(); + }); + }); + + describe("claim()", () => { + it("should calculate the rewards earned for a user correctly", async () => { + await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); + + await staking.connect(FIRST).stake(wei(100, sharesDecimals)); + + await time.setNextBlockTimestamp((await time.latest()) + 30); + + await staking.connect(FIRST).unstake(wei(100, sharesDecimals)); + + expect(await staking.getOwedValue(FIRST)).to.equal(wei(30, rewardsDecimals)); + expect(await staking.userOwedValue(FIRST)).to.equal(wei(30, rewardsDecimals)); + }); + + it("should calculate the reward earned for multiple users correctly", async () => { + await performStakingManipulations(); + + await checkManipulationRewards(); + }); + + it("should calculate the reward earned for multiple users correctly", async () => { + await performStakingManipulations2(); + + await checkManipulationRewards2(); + }); + + it("should claim all the rewards correctly", async () => { + await performStakingManipulations(); + + await staking.connect(FIRST).claim(await staking.getOwedValue(FIRST)); + await staking.connect(SECOND).claim(await staking.getOwedValue(SECOND)); + await staking.connect(THIRD).claim(await staking.getOwedValue(THIRD)); + + expect(await staking.getOwedValue(FIRST)).to.equal(0); + expect(await staking.getOwedValue(SECOND)).to.equal(0); + expect(await staking.getOwedValue(THIRD)).to.equal(0); + + expect(await staking.userOwedValue(FIRST)).to.equal(0); + expect(await staking.userOwedValue(SECOND)).to.equal(0); + expect(await staking.userOwedValue(THIRD)).to.equal(0); + }); + + it("should correctly claim rewards partially", async () => { + await performStakingManipulations(); + + await staking.connect(FIRST).claim((await staking.getOwedValue(FIRST)) - wei(1, rewardsDecimals)); + await staking.connect(SECOND).claim((await staking.getOwedValue(SECOND)) - wei(2, rewardsDecimals)); + await staking.connect(THIRD).claim((await staking.getOwedValue(THIRD)) - wei(3, rewardsDecimals)); + + expect(await staking.getOwedValue(FIRST)).to.equal(wei(1, rewardsDecimals)); + expect(await staking.getOwedValue(SECOND)).to.equal(wei(2, rewardsDecimals)); + expect(await staking.getOwedValue(THIRD)).to.equal(wei(3, rewardsDecimals)); + + expect(await staking.userOwedValue(FIRST)).to.equal(wei(1, rewardsDecimals)); + expect(await staking.userOwedValue(SECOND)).to.equal(wei(2, rewardsDecimals)); + expect(await staking.userOwedValue(THIRD)).to.equal(wei(3, rewardsDecimals)); + }); + + it("should allow to claim rewards in several rounds correctly", async () => { + await performStakingManipulations2(); + + await staking.connect(FIRST).claim((await staking.getOwedValue(FIRST)) - wei(3)); + + expect(await staking.getOwedValue(FIRST)).to.equal(wei(3, rewardsDecimals)); + expect(await staking.userOwedValue(FIRST)).to.equal(wei(3, rewardsDecimals)); + + await staking.connect(FIRST).claim((await staking.getOwedValue(FIRST)) - wei(2)); + + expect(await staking.getOwedValue(FIRST)).to.equal(wei(2, rewardsDecimals)); + expect(await staking.userOwedValue(FIRST)).to.equal(wei(2, rewardsDecimals)); + + await staking.connect(FIRST).claim(wei(2, rewardsDecimals)); + + expect(await staking.getOwedValue(FIRST)).to.equal(0); + expect(await staking.userOwedValue(FIRST)).to.equal(0); + }); + + it("should transfer tokens correctly on the claim", async () => { + await performStakingManipulations(); + + const initialRewardsBalance = await rewardsToken.balanceOf(staking); + + const firstOwed = await staking.getOwedValue(FIRST); + const secondOwed = await staking.getOwedValue(SECOND); + const thirdOwed = await staking.getOwedValue(THIRD); + + await staking.connect(FIRST).claim(firstOwed); + await staking.connect(SECOND).claim(secondOwed); + + await staking.connect(THIRD).claim(thirdOwed); + + expect(await rewardsToken.balanceOf(staking)).to.equal( + initialRewardsBalance - (firstOwed + secondOwed + thirdOwed), + ); + expect(await rewardsToken.balanceOf(FIRST)).to.equal(firstOwed); + expect(await rewardsToken.balanceOf(SECOND)).to.equal(secondOwed); + expect(await rewardsToken.balanceOf(THIRD)).to.equal(thirdOwed); + }); + + it("should not allow to claim 0 rewards", async () => { + await expect(staking.connect(FIRST).claim(0)).to.be.revertedWithCustomError(staking, "AmountIsZero").withArgs(); + }); + + it("should not allow to claim more rewards than earned", async () => { + await performStakingManipulations(); + + await expect(staking.connect(FIRST).claim(wei(4, rewardsDecimals))) + .to.be.revertedWithCustomError(staking, "InsufficientOwedValue") + .withArgs(FIRST.address, staking.getOwedValue(FIRST.address), wei(4, rewardsDecimals)); + }); + }); + + describe("claimAll()", () => { + it("should claim all the rewards correctly", async () => { + await performStakingManipulations(); + + await staking.connect(FIRST).claimAll(); + await staking.connect(SECOND).claimAll(); + await staking.connect(THIRD).claimAll(); + + expect(await staking.getOwedValue(FIRST)).to.equal(0); + expect(await staking.getOwedValue(SECOND)).to.equal(0); + expect(await staking.getOwedValue(THIRD)).to.equal(0); + + expect(await staking.userOwedValue(FIRST)).to.equal(0); + expect(await staking.userOwedValue(SECOND)).to.equal(0); + expect(await staking.userOwedValue(THIRD)).to.equal(0); + }); + + it("should transfer tokens correctly on the claim", async () => { + await performStakingManipulations(); + + const initialRewardsBalance = await rewardsToken.balanceOf(staking); + + const firstOwed = await staking.getOwedValue(FIRST); + const secondOwed = await staking.getOwedValue(SECOND); + const thirdOwed = await staking.getOwedValue(THIRD); + + expect(await staking.connect(FIRST).claimAll.staticCall()).to.eq(firstOwed); + expect(await staking.connect(SECOND).claimAll.staticCall()).to.eq(secondOwed); + expect(await staking.connect(THIRD).claimAll.staticCall()).to.eq(thirdOwed); + + await staking.connect(FIRST).claimAll(); + await staking.connect(SECOND).claimAll(); + + await staking.connect(THIRD).claimAll(); + + expect(await rewardsToken.balanceOf(staking)).to.equal( + initialRewardsBalance - (firstOwed + secondOwed + thirdOwed), + ); + expect(await rewardsToken.balanceOf(FIRST)).to.equal(firstOwed); + expect(await rewardsToken.balanceOf(SECOND)).to.equal(secondOwed); + expect(await rewardsToken.balanceOf(THIRD)).to.equal(thirdOwed); + }); + + it("should not allow to claim 0 rewards", async () => { + await performStakingManipulations(); + + await expect( + staking.multicall([ + staking.interface.encodeFunctionData("claimAll"), + staking.interface.encodeFunctionData("claimAll"), + ]), + ) + .to.be.revertedWithCustomError(staking, "AmountIsZero") + .withArgs(); + }); + }); + + describe("rate", () => { + it("should accept 0 as a rate and calculate owed values according to this rate correctly", async () => { + const StakingMock = await ethers.getContractFactory("StakingMock"); + staking = await StakingMock.deploy(); + + await staking.__StakingMock_init(sharesToken, rewardsToken, 0, stakingStartTime); + + await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); + + await staking.connect(FIRST).stake(wei(100, sharesDecimals)); + + await time.setNextBlockTimestamp((await time.latest()) + 20); + + await staking.connect(FIRST).unstake(wei(100, sharesDecimals)); + + expect(await staking.rate()).to.equal(0); + + expect(await staking.getOwedValue(FIRST)).to.equal(0); + expect(await staking.userOwedValue(FIRST)).to.equal(0); + expect(await staking.cumulativeSum()).to.equal(0); + }); + + it("should calculate owed value properly after the rate is changed to 0", async () => { + const StakingMock = await ethers.getContractFactory("StakingMock"); + staking = await StakingMock.deploy(); + + await staking.__StakingMock_init(sharesToken, rewardsToken, rate, stakingStartTime); + + await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); + await mintAndApproveTokens(SECOND, sharesToken, wei(300, sharesDecimals)); + + await staking.connect(FIRST).stake(wei(50, sharesDecimals)); + await staking.connect(SECOND).stake(wei(150, sharesDecimals)); + + await staking.connect(FIRST).unstake(wei(50, sharesDecimals)); + await staking.connect(SECOND).unstake(wei(100, sharesDecimals)); + + await staking.setRate(0); + + expect(await staking.rate()).to.equal(0); + + await staking.connect(SECOND).unstake(wei(50, sharesDecimals)); + + let firstOwedValue = await staking.getOwedValue(FIRST); + let secondOwedValue = await staking.getOwedValue(SECOND); + + await performStakingManipulations(); + + expect(await staking.userOwedValue(FIRST)).to.equal(firstOwedValue); + expect(await staking.userOwedValue(SECOND)).to.equal(secondOwedValue); + }); + + it("should work as expected after updating the rate", async () => { + const StakingMock = await ethers.getContractFactory("StakingMock"); + staking = await StakingMock.deploy(); + + await staking.__StakingMock_init(sharesToken, rewardsToken, rate, stakingStartTime); + + await mintAndApproveTokens(FIRST, sharesToken, wei(100, sharesDecimals)); + await mintAndApproveTokens(SECOND, sharesToken, wei(300, sharesDecimals)); + + await staking.connect(FIRST).stake(wei(50, sharesDecimals)); + await staking.connect(SECOND).stake(wei(150, sharesDecimals)); + + await staking.connect(FIRST).unstake(wei(50, sharesDecimals)); + await staking.connect(SECOND).unstake(wei(100, sharesDecimals)); + + const prevCumulativeSum = await staking.cumulativeSum(); + + let firstOwedValue = await staking.getOwedValue(FIRST); + let secondOwedValue = await staking.getOwedValue(SECOND); + + await staking.setRate(wei(2, rewardsDecimals)); + + const expectedCumulativeSum = prevCumulativeSum + wei(rate, 25) / (await staking.totalShares()); + + expect(await staking.rate()).to.equal(wei(2, rewardsDecimals)); + expect(await staking.cumulativeSum()).to.equal(expectedCumulativeSum); + expect(await staking.updatedAt()).to.equal(await time.latest()); + + expect(await staking.userOwedValue(FIRST)).to.equal(firstOwedValue); + expect(await staking.userOwedValue(SECOND)).to.equal(secondOwedValue); + }); + }); + + describe("should handle staking manipulations with 6-decimal values", () => { + it("should handle the whole staling process using 6-decimal values", async () => { + const StakingMock = await ethers.getContractFactory("StakingMock"); + const ERC20Mock = await ethers.getContractFactory("ERC20Mock"); + + staking = await StakingMock.deploy(); + sharesToken = await ERC20Mock.deploy("SharesMock", "SMock", 6); + rewardsToken = await ERC20Mock.deploy("RewardsMock", "RMock", 6); + + sharesDecimals = Number(await sharesToken.decimals()); + rewardsDecimals = Number(await rewardsToken.decimals()); + + await rewardsToken.mint(await staking.getAddress(), wei(100, rewardsDecimals)); + + await staking.__StakingMock_init(sharesToken, rewardsToken, wei(1, rewardsDecimals), 3n); + + await performStakingManipulations2(); + + await checkManipulationRewards2(); + }); + }); +}); diff --git a/test/finance/staking/ValueDistributor.test.ts b/test/finance/staking/ValueDistributor.test.ts new file mode 100644 index 00000000..fadd94af --- /dev/null +++ b/test/finance/staking/ValueDistributor.test.ts @@ -0,0 +1,351 @@ +import { ethers } from "hardhat"; +import { expect } from "chai"; + +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; +import { time } from "@nomicfoundation/hardhat-network-helpers"; + +import { Reverter } from "@/test/helpers/reverter"; +import { wei } from "@/scripts/utils/utils"; + +import { ValueDistributorMock } from "@ethers-v6"; + +describe("ValueDistributor", () => { + const reverter = new Reverter(); + + let FIRST: SignerWithAddress; + let SECOND: SignerWithAddress; + let THIRD: SignerWithAddress; + let FOURTH: SignerWithAddress; + + let valueDistributor: ValueDistributorMock; + + const addSharesToAllUsers = async (shares: Array) => { + await valueDistributor.addShares(FIRST, shares[0]); + await valueDistributor.addShares(SECOND, shares[1]); + await valueDistributor.addShares(THIRD, shares[2]); + await valueDistributor.addShares(FOURTH, shares[3]); + }; + + const removeSharesFromAllUsers = async (shares: Array) => { + await valueDistributor.removeShares(FIRST, shares[0]); + await valueDistributor.removeShares(SECOND, shares[1]); + await valueDistributor.removeShares(THIRD, shares[2]); + await valueDistributor.removeShares(FOURTH, shares[3]); + }; + + const checkAllShares = async (shares: Array) => { + expect(await valueDistributor.userShares(FIRST)).to.equal(shares[0]); + expect(await valueDistributor.userShares(SECOND)).to.equal(shares[1]); + expect(await valueDistributor.userShares(THIRD)).to.equal(shares[2]); + expect(await valueDistributor.userShares(FOURTH)).to.equal(shares[3]); + }; + + const performSharesManipulations = async () => { + await time.setNextBlockTimestamp((await time.latest()) + 2); + + await valueDistributor.addShares(FIRST, 100); + + await time.setNextBlockTimestamp((await time.latest()) + 2); + + await valueDistributor.addShares(SECOND, 200); + + await valueDistributor.addShares(THIRD, 100); + + await time.setNextBlockTimestamp((await time.latest()) + 3); + + await valueDistributor.removeShares(FIRST, 100); + + await valueDistributor.addShares(THIRD, 100); + + await valueDistributor.removeShares(SECOND, 200); + + await time.setNextBlockTimestamp((await time.latest()) + 2); + + await valueDistributor.removeShares(THIRD, 200); + }; + + before("setup", async () => { + [FIRST, SECOND, THIRD, FOURTH] = await ethers.getSigners(); + + const valueDistributorMock = await ethers.getContractFactory("ValueDistributorMock"); + valueDistributor = await valueDistributorMock.deploy(); + + await reverter.snapshot(); + }); + + afterEach(reverter.revert); + + describe("addShares()", () => { + it("should add shares correctly", async () => { + await addSharesToAllUsers([1, 2, 3, 4]); + await valueDistributor.addShares(FIRST, 5); + + await checkAllShares([6, 2, 3, 4]); + expect(await valueDistributor.totalShares()).to.equal(15); + }); + + it("should not allow to add 0 shares", async () => { + await expect(valueDistributor.addShares(SECOND, 0)) + .to.be.revertedWithCustomError(valueDistributor, "AmountIsZero") + .withArgs(); + }); + + it("should not allow zero address to add shares", async () => { + await expect(valueDistributor.addShares(ethers.ZeroAddress, 2)) + .to.be.revertedWithCustomError(valueDistributor, "UserIsZeroAddress") + .withArgs(); + }); + }); + + describe("removeShares()", () => { + it("should correctly remove shares partially", async () => { + await addSharesToAllUsers([3, 2, 3, 4]); + + await removeSharesFromAllUsers([1, 1, 1, 2]); + await valueDistributor.removeShares(THIRD, 1); + + await checkAllShares([2, 1, 1, 2]); + expect(await valueDistributor.totalShares()).to.equal(6); + }); + + it("should handle removing all the shares correctly", async () => { + await addSharesToAllUsers([2, 1, 1, 2]); + + await removeSharesFromAllUsers([2, 1, 1, 2]); + + await checkAllShares([0, 0, 0, 0]); + + expect(await valueDistributor.totalShares()).to.equal(0); + + const cumulativeSum = await valueDistributor.cumulativeSum(); + + await valueDistributor.addShares(FIRST, 2); + + expect(await valueDistributor.cumulativeSum()).to.equal(cumulativeSum); + expect(await valueDistributor.totalShares()).to.equal(2); + expect(await valueDistributor.userShares(FIRST)).to.equal(2); + }); + + it("should not allow to remove 0 shares", async () => { + await expect(valueDistributor.removeShares(SECOND, 0)) + .to.be.revertedWithCustomError(valueDistributor, "AmountIsZero") + .withArgs(); + }); + + it("should not allow zero address to remove shares", async () => { + await expect(valueDistributor.removeShares(ethers.ZeroAddress, 2)) + .to.be.revertedWithCustomError(valueDistributor, "InsufficientSharesAmount") + .withArgs(ethers.ZeroAddress, 0, 2); + }); + + it("should not allow to remove more shares than it was added", async () => { + await expect(valueDistributor.removeShares(SECOND, 1)) + .to.be.revertedWithCustomError(valueDistributor, "InsufficientSharesAmount") + .withArgs(SECOND, (await valueDistributor.userDistribution(SECOND)).shares, 1); + }); + }); + + describe("distributeValue()", () => { + it("should calculate the value owed to a user correctly", async () => { + await valueDistributor.addShares(FIRST, 100); + + await time.setNextBlockTimestamp((await time.latest()) + 30); + + await valueDistributor.removeShares(FIRST, 100); + + expect(await valueDistributor.getOwedValue(FIRST)).to.equal(wei(30)); + expect(await valueDistributor.userOwedValue(FIRST)).to.equal(wei(30)); + }); + + it("should calculate the value owed to multiple users correctly", async () => { + await performSharesManipulations(); + + const firstExpectedReward = wei(3) + wei(1) / 12n; + const secondExpectedReward = wei(3) + wei(1) / 3n; + const thirdExpectedReward = wei(3) + wei(7) / 12n; + + expect(await valueDistributor.getOwedValue(FIRST)).to.equal(firstExpectedReward); + expect(await valueDistributor.getOwedValue(SECOND)).to.equal(secondExpectedReward); + expect(await valueDistributor.getOwedValue(THIRD)).to.equal(thirdExpectedReward); + + expect(await valueDistributor.userOwedValue(FIRST)).to.equal(firstExpectedReward); + expect(await valueDistributor.userOwedValue(SECOND)).to.equal(secondExpectedReward); + expect(await valueDistributor.userOwedValue(THIRD)).to.equal(thirdExpectedReward); + }); + + it("should calculate the value owed to multiple users correctly", async () => { + await valueDistributor.addShares(FIRST, 200); + + await time.setNextBlockTimestamp((await time.latest()) + 3); + + await valueDistributor.addShares(SECOND, 100); + + await valueDistributor.addShares(THIRD, 300); + + await time.setNextBlockTimestamp((await time.latest()) + 3); + + await valueDistributor.addShares(FIRST, 200); + + await valueDistributor.removeShares(FIRST, 100); + + await time.setNextBlockTimestamp((await time.latest()) + 3); + + await valueDistributor.removeShares(SECOND, 100); + + await valueDistributor.addShares(THIRD, 100); + + await time.setNextBlockTimestamp((await time.latest()) + 2); + + await valueDistributor.removeShares(FIRST, 300); + + await valueDistributor.removeShares(THIRD, 400); + + const firstExpectedReward = wei(7) + wei(2) / 3n + wei(1) / 7n; + const secondExpectedReward = wei(233) / 168n; + const thirdExpectedReward = wei(5) + wei(3) / 8n + wei(3) / 7n; + + expect(await valueDistributor.getOwedValue(FIRST)).to.equal(firstExpectedReward); + expect(await valueDistributor.getOwedValue(SECOND)).to.equal(secondExpectedReward); + expect(await valueDistributor.getOwedValue(THIRD)).to.equal(thirdExpectedReward); + + expect(await valueDistributor.userOwedValue(FIRST)).to.equal(firstExpectedReward); + expect(await valueDistributor.userOwedValue(SECOND)).to.equal(secondExpectedReward); + expect(await valueDistributor.userOwedValue(THIRD)).to.equal(thirdExpectedReward); + }); + + it("should distribute all the owed values correctly", async () => { + await performSharesManipulations(); + + await valueDistributor.distributeValue(FIRST, await valueDistributor.getOwedValue(FIRST)); + await valueDistributor.distributeValue(SECOND, await valueDistributor.getOwedValue(SECOND)); + await valueDistributor.distributeValue(THIRD, await valueDistributor.getOwedValue(THIRD)); + + expect(await valueDistributor.getOwedValue(FIRST)).to.equal(0); + expect(await valueDistributor.getOwedValue(SECOND)).to.equal(0); + expect(await valueDistributor.getOwedValue(THIRD)).to.equal(0); + + expect(await valueDistributor.userOwedValue(FIRST)).to.equal(0); + expect(await valueDistributor.userOwedValue(SECOND)).to.equal(0); + expect(await valueDistributor.userOwedValue(THIRD)).to.equal(0); + }); + + it("should distribute all the owed values optimally", async () => { + await performSharesManipulations(); + + const firstOwed = await valueDistributor.getOwedValue(FIRST); + const secondOwed = await valueDistributor.getOwedValue(SECOND); + const thirdOwed = await valueDistributor.getOwedValue(THIRD); + + expect(await valueDistributor.distributeAllValue.staticCall(FIRST)).to.eq(firstOwed); + expect(await valueDistributor.distributeAllValue.staticCall(SECOND)).to.eq(secondOwed); + expect(await valueDistributor.distributeAllValue.staticCall(THIRD)).to.eq(thirdOwed); + + await valueDistributor.distributeAllValue(FIRST); + await valueDistributor.distributeAllValue(SECOND); + await valueDistributor.distributeAllValue(THIRD); + + expect(await valueDistributor.getOwedValue(FIRST)).to.equal(0); + expect(await valueDistributor.getOwedValue(SECOND)).to.equal(0); + expect(await valueDistributor.getOwedValue(THIRD)).to.equal(0); + + expect(await valueDistributor.userOwedValue(FIRST)).to.equal(0); + expect(await valueDistributor.userOwedValue(SECOND)).to.equal(0); + expect(await valueDistributor.userOwedValue(THIRD)).to.equal(0); + }); + + it("should correctly distribute owed values partially", async () => { + await performSharesManipulations(); + + await valueDistributor.distributeValue(FIRST, (await valueDistributor.getOwedValue(FIRST)) - wei(1)); + await valueDistributor.distributeValue(SECOND, (await valueDistributor.getOwedValue(SECOND)) - wei(2)); + await valueDistributor.distributeValue(THIRD, (await valueDistributor.getOwedValue(THIRD)) - wei(3)); + + expect(await valueDistributor.getOwedValue(FIRST)).to.equal(wei(1)); + expect(await valueDistributor.getOwedValue(SECOND)).to.equal(wei(2)); + expect(await valueDistributor.getOwedValue(THIRD)).to.equal(wei(3)); + + expect(await valueDistributor.userOwedValue(FIRST)).to.equal(wei(1)); + expect(await valueDistributor.userOwedValue(SECOND)).to.equal(wei(2)); + expect(await valueDistributor.userOwedValue(THIRD)).to.equal(wei(3)); + }); + + it("should allow to distribute values in several rounds correctly", async () => { + await performSharesManipulations(); + + await valueDistributor.distributeValue(FIRST, (await valueDistributor.getOwedValue(FIRST)) - wei(3)); + + expect(await valueDistributor.getOwedValue(FIRST)).to.equal(wei(3)); + expect(await valueDistributor.userOwedValue(FIRST)).to.equal(wei(3)); + + await valueDistributor.distributeValue(FIRST, (await valueDistributor.getOwedValue(FIRST)) - wei(2)); + + expect(await valueDistributor.getOwedValue(FIRST)).to.equal(wei(2)); + expect(await valueDistributor.userOwedValue(FIRST)).to.equal(wei(2)); + + await valueDistributor.distributeValue(FIRST, wei(2)); + + expect(await valueDistributor.getOwedValue(FIRST)).to.equal(0); + expect(await valueDistributor.userOwedValue(FIRST)).to.equal(wei(0)); + }); + + it("should not allow to distribute 0 values", async () => { + await expect(valueDistributor.distributeValue(FIRST, 0)) + .to.be.revertedWithCustomError(valueDistributor, "AmountIsZero") + .withArgs(); + }); + + it("should not allow zero address to distribute values", async () => { + await expect(valueDistributor.distributeValue(ethers.ZeroAddress, 2)) + .to.be.revertedWithCustomError(valueDistributor, "InsufficientOwedValue") + .withArgs(ethers.ZeroAddress, 0, 2); + }); + + it("should not allow to distribute more values than owed", async () => { + await performSharesManipulations(); + + await expect(valueDistributor.distributeValue(FIRST, wei(4))) + .to.be.revertedWithCustomError(valueDistributor, "InsufficientOwedValue") + .withArgs(FIRST, valueDistributor.getOwedValue(FIRST), wei(4)); + }); + }); + + describe("same block transactions", () => { + it("should work as expected if more than one transaction which updates the key values is sent within one block", async () => { + await valueDistributor.addShares(FIRST, 100); + + await valueDistributor.multicall([ + valueDistributor.interface.encodeFunctionData("addShares", [await FIRST.getAddress(), 200]), + valueDistributor.interface.encodeFunctionData("addShares", [await SECOND.getAddress(), 200]), + ]); + + await time.setNextBlockTimestamp((await time.latest()) + 3); + + await valueDistributor.removeShares(SECOND, 100); + + await time.setNextBlockTimestamp((await time.latest()) + 4); + + await valueDistributor.addShares(THIRD, 100); + + await valueDistributor.multicall([ + valueDistributor.interface.encodeFunctionData("removeShares", [await FIRST.getAddress(), 300]), + valueDistributor.interface.encodeFunctionData("removeShares", [await SECOND.getAddress(), 100]), + ]); + + await time.setNextBlockTimestamp((await time.latest()) + 3); + + await valueDistributor.removeShares(THIRD, 100); + + const firstExpectedReward = wei(6) + wei(2) / 5n; + const secondExpectedReward = wei(2) + wei(2) / 5n; + const thirdExpectedReward = wei(3) + wei(1) / 5n; + + expect(await valueDistributor.getOwedValue(FIRST)).to.equal(firstExpectedReward); + expect(await valueDistributor.getOwedValue(SECOND)).to.equal(secondExpectedReward); + expect(await valueDistributor.getOwedValue(THIRD)).to.equal(thirdExpectedReward); + + expect(await valueDistributor.userOwedValue(FIRST)).to.equal(firstExpectedReward); + expect(await valueDistributor.userOwedValue(SECOND)).to.equal(secondExpectedReward); + expect(await valueDistributor.userOwedValue(THIRD)).to.equal(thirdExpectedReward); + }); + }); +}); diff --git a/test/finance/vesting/Vesting.test.ts b/test/finance/vesting/Vesting.test.ts index a8518853..c20d1315 100644 --- a/test/finance/vesting/Vesting.test.ts +++ b/test/finance/vesting/Vesting.test.ts @@ -1,25 +1,26 @@ -import { MAX_UINT256, ZERO_ADDR } from "@/scripts/utils/constants"; -import { precision, wei } from "@/scripts/utils/utils"; -import { Reverter } from "@/test/helpers/reverter"; +import { ethers } from "hardhat"; +import { expect } from "chai"; + import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { time } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; -import { ERC20Mock, ERC20Mock__factory, Vesting, VestingMock, VestingMock__factory } from "@ethers-v6"; +import { Reverter } from "@/test/helpers/reverter"; +import { precision, wei } from "@/scripts/utils/utils"; + +import { ERC20Mock, ERC20Mock__factory, AVesting, VestingMock, VestingMock__factory } from "@ethers-v6"; describe("Vesting", () => { let reverter = new Reverter(); - let owner: SignerWithAddress; - let alice: SignerWithAddress; + let OWNER: SignerWithAddress; + let ALICE: SignerWithAddress; let vesting: VestingMock; let erc20: ERC20Mock; - type BaseSchedule = Vesting.BaseScheduleStruct; - type Schedule = Vesting.ScheduleStruct; - type Vesting = Vesting.VestingDataStruct; + type BaseSchedule = AVesting.BaseScheduleStruct; + type Schedule = AVesting.ScheduleStruct; + type Vesting = AVesting.VestingDataStruct; const LINEAR_EXPONENT = 1n; @@ -30,15 +31,15 @@ describe("Vesting", () => { const exponent = 4n; before(async () => { - [owner, alice] = await ethers.getSigners(); + [OWNER, ALICE] = await ethers.getSigners(); - vesting = await new VestingMock__factory(owner).deploy(); - erc20 = await new ERC20Mock__factory(owner).deploy("Test", "TST", 18); + vesting = await new VestingMock__factory(OWNER).deploy(); + erc20 = await new ERC20Mock__factory(OWNER).deploy("Test", "TST", 18); await vesting.__VestingMock_init(); - await erc20.mint(owner.address, wei(1_000_000)); - await erc20.approve(await vesting.getAddress(), MAX_UINT256); + await erc20.mint(OWNER.address, wei(1_000_000)); + await erc20.approve(await vesting.getAddress(), ethers.MaxUint256); await reverter.snapshot(); }); @@ -55,8 +56,10 @@ describe("Vesting", () => { describe("access", () => { it("should not initialize twice", async () => { - await expect(vesting.__VestingMock_init()).to.be.revertedWith("Initializable: contract is already initialized"); - await expect(vesting.vestingInit()).to.be.revertedWith("Initializable: contract is not initializing"); + await expect(vesting.__VestingMock_init()) + .to.be.revertedWithCustomError(vesting, "InvalidInitialization") + .withArgs(); + await expect(vesting.vestingInit()).to.be.revertedWithCustomError(vesting, "NotInitializing").withArgs(); }); }); @@ -87,34 +90,35 @@ describe("Vesting", () => { it("should revert if duration periods is 0", async () => { let baseSchedule = { secondsInPeriod, durationInPeriods: 0, cliffInPeriods } as BaseSchedule; - await expect(vesting.createBaseSchedule(baseSchedule)).to.be.revertedWith( - "VestingWallet: cannot create schedule with zero duration or zero seconds in period", - ); + await expect(vesting.createBaseSchedule(baseSchedule)) + .to.be.revertedWithCustomError(vesting, "ScheduleInvalidPeriodParameter") + .withArgs(0, secondsInPeriod); }); it("should revert if seconds in period is 0", async () => { let baseSchedule = { secondsInPeriod: 0, durationInPeriods, cliffInPeriods } as BaseSchedule; - await expect(vesting.createBaseSchedule(baseSchedule)).to.be.revertedWith( - "VestingWallet: cannot create schedule with zero duration or zero seconds in period", - ); + await expect(vesting.createBaseSchedule(baseSchedule)) + .to.be.revertedWithCustomError(vesting, "ScheduleInvalidPeriodParameter") + .withArgs(durationInPeriods, 0); }); it("should revert if cliff is greater than duration", async () => { - let baseSchedule = { secondsInPeriod, durationInPeriods, cliffInPeriods: durationInPeriods + 1n } as BaseSchedule; + const wrongCliff = durationInPeriods + 1n; + let baseSchedule = { secondsInPeriod, durationInPeriods, cliffInPeriods: wrongCliff } as BaseSchedule; - await expect(vesting.createBaseSchedule(baseSchedule)).to.be.revertedWith( - "VestingWallet: cliff cannot be greater than duration", - ); + await expect(vesting.createBaseSchedule(baseSchedule)) + .to.be.revertedWithCustomError(vesting, "ScheduleCliffGreaterThanDuration") + .withArgs(wrongCliff, durationInPeriods); }); it("should revert if exponent is 0", async () => { let baseSchedule = { secondsInPeriod, durationInPeriods, cliffInPeriods } as BaseSchedule; let schedule = { scheduleData: baseSchedule, exponent: 0 } as Schedule; - await expect(vesting.createSchedule(schedule)).to.be.revertedWith( - "VestingWallet: cannot create schedule with zero exponent", - ); + await expect(vesting.createSchedule(schedule)) + .to.be.revertedWithCustomError(vesting, "ExponentIsZero") + .withArgs(); }); }); @@ -138,7 +142,7 @@ describe("Vesting", () => { defaultVesting = { vestingStartTime: await time.latest(), - beneficiary: alice.address, + beneficiary: ALICE.address, vestingToken: await erc20.getAddress(), vestingAmount: vestingAmount, paidAmount: 0, @@ -149,7 +153,7 @@ describe("Vesting", () => { it("should correctly create vesting", async () => { let linearVesting = { vestingStartTime: await time.latest(), - beneficiary: alice.address, + beneficiary: ALICE.address, vestingToken: await erc20.getAddress(), vestingAmount: vestingAmount, paidAmount: 0, @@ -174,8 +178,8 @@ describe("Vesting", () => { expect(await vesting.getVesting(1)).to.deep.equal(Object.values(linearVesting)); expect(await vesting.getVesting(2)).to.deep.equal(Object.values(exponentialVesting)); - expect(await vesting.getVestingIds(await alice.getAddress())).to.deep.equal([1, 2]); - expect(await vesting.getVestings(await alice.getAddress())).to.deep.equal([ + expect(await vesting.getVestingIds(await ALICE.getAddress())).to.deep.equal([1, 2]); + expect(await vesting.getVestings(await ALICE.getAddress())).to.deep.equal([ Object.values(linearVesting), Object.values(exponentialVesting), ]); @@ -190,41 +194,41 @@ describe("Vesting", () => { it("should revert if vesting start time is zero", async () => { defaultVesting.vestingStartTime = 0; - await expect(vesting.createVesting(defaultVesting)).to.be.revertedWith( - "VestingWallet: cannot create vesting for zero time", - ); + await expect(vesting.createVesting(defaultVesting)) + .to.be.revertedWithCustomError(vesting, "StartTimeIsZero") + .withArgs(); }); it("should revert if vesting amount is zero", async () => { defaultVesting.vestingAmount = 0; - await expect(vesting.createVesting(defaultVesting)).to.be.revertedWith( - "VestingWallet: cannot create vesting for zero amount", - ); + await expect(vesting.createVesting(defaultVesting)) + .to.be.revertedWithCustomError(vesting, "VestingAmountIsZero") + .withArgs(); }); it("should revert if vesting beneficiary is zero address", async () => { - defaultVesting.beneficiary = ZERO_ADDR; + defaultVesting.beneficiary = ethers.ZeroAddress; - await expect(vesting.createVesting(defaultVesting)).to.be.revertedWith( - "VestingWallet: cannot create vesting for zero address", - ); + await expect(vesting.createVesting(defaultVesting)) + .to.be.revertedWithCustomError(vesting, "BeneficiaryIsZeroAddress") + .withArgs(); }); it("should revert if vesting token is zero address", async () => { - defaultVesting.vestingToken = ZERO_ADDR; + defaultVesting.vestingToken = ethers.ZeroAddress; - await expect(vesting.createVesting(defaultVesting)).to.be.revertedWith( - "VestingWallet: vesting token cannot be zero address", - ); + await expect(vesting.createVesting(defaultVesting)) + .to.be.revertedWithCustomError(vesting, "VestingTokenIsZeroAddress") + .withArgs(); }); it("should revert if vesting created for a past date", async () => { await time.increase(secondsInPeriod * durationInPeriods); - await expect(vesting.createVesting(defaultVesting)).to.be.revertedWith( - "VestingWallet: cannot create vesting for a past date", - ); + await expect(vesting.createVesting(defaultVesting)) + .to.be.revertedWithCustomError(vesting, "VestingPastDate") + .withArgs(); }); }); @@ -244,7 +248,7 @@ describe("Vesting", () => { linearVesting = { vestingStartTime: await time.latest(), - beneficiary: alice.address, + beneficiary: ALICE.address, vestingToken: await erc20.getAddress(), vestingAmount: vestingAmount, paidAmount: 0, @@ -279,8 +283,8 @@ describe("Vesting", () => { expect(await vesting.getVestedAmount(exponentialVestingId)).to.be.equal(exponentialVesting.vestingAmount); expect(await vesting.getWithdrawableAmount(exponentialVestingId)).to.be.equal(exponentialVesting.vestingAmount); - const linearTx = vesting.connect(alice).withdrawFromVesting(linearVestingId); - const exponentialTx = vesting.connect(alice).withdrawFromVesting(exponentialVestingId); + const linearTx = vesting.connect(ALICE).withdrawFromVesting(linearVestingId); + const exponentialTx = vesting.connect(ALICE).withdrawFromVesting(exponentialVestingId); await expect(linearTx) .to.emit(vesting, "WithdrawnFromVesting") @@ -295,9 +299,9 @@ describe("Vesting", () => { expect(await vesting.getVesting(linearVestingId)).to.deep.equal(Object.values(linearVesting)); expect(await vesting.getVesting(exponentialVestingId)).to.deep.equal(Object.values(exponentialVesting)); - expect(await erc20.balanceOf(alice.address)).changeTokenBalance( + expect(await erc20.balanceOf(ALICE.address)).changeTokenBalance( erc20, - alice.address, + ALICE.address, BigInt(linearVesting.vestingAmount) + BigInt(exponentialVesting.vestingAmount), ); }); @@ -336,8 +340,8 @@ describe("Vesting", () => { expect(await vesting.getVestedAmount(exponentialVestingId)).to.be.equal(exponentialVestedAmount); expect(await vesting.getWithdrawableAmount(exponentialVestingId)).to.be.equal(exponentialVestedAmount); - await vesting.connect(alice).withdrawFromVesting(linearVestingId); - await vesting.connect(alice).withdrawFromVesting(exponentialVestingId); + await vesting.connect(ALICE).withdrawFromVesting(linearVestingId); + await vesting.connect(ALICE).withdrawFromVesting(exponentialVestingId); linearVesting.paidAmount = linearVestedAmount; exponentialVesting.paidAmount = exponentialVestedAmount; @@ -348,9 +352,9 @@ describe("Vesting", () => { expect(await vesting.getWithdrawableAmount(linearVestingId)).to.be.equal(0); expect(await vesting.getWithdrawableAmount(exponentialVestingId)).to.be.equal(0); - expect(await erc20.balanceOf(alice.address)).changeTokenBalance( + expect(await erc20.balanceOf(ALICE.address)).changeTokenBalance( erc20, - alice.address, + ALICE.address, linearVestedAmount + exponentialVestedAmount, ); }); @@ -358,9 +362,9 @@ describe("Vesting", () => { it("should revert if non beneficiary tries to withdraw from vesting", async () => { await vesting.createVesting(linearVesting); - await expect(vesting.withdrawFromVesting(1)).to.be.revertedWith( - "VestingWallet: only beneficiary can withdraw from his vesting", - ); + await expect(vesting.withdrawFromVesting(1)) + .to.be.revertedWithCustomError(vesting, "UnauthorizedAccount") + .withArgs(OWNER); }); it("should revert if nothing to withdraw", async () => { @@ -368,11 +372,11 @@ describe("Vesting", () => { await time.increase(secondsInPeriod * durationInPeriods); - await vesting.connect(alice).withdrawFromVesting(1); + await vesting.connect(ALICE).withdrawFromVesting(1); - await expect(vesting.connect(alice).withdrawFromVesting(1)).to.be.revertedWith( - "VestingWallet: nothing to withdraw", - ); + await expect(vesting.connect(ALICE).withdrawFromVesting(1)) + .to.be.revertedWithCustomError(vesting, "NothingToWithdraw") + .withArgs(); }); }); diff --git a/test/helpers/merkle-tree-helper.ts b/test/helpers/merkle-tree-helper.ts index c33e1349..947f66a5 100644 --- a/test/helpers/merkle-tree-helper.ts +++ b/test/helpers/merkle-tree-helper.ts @@ -2,13 +2,11 @@ import { ethers } from "hardhat"; import { MerkleTree } from "merkletreejs"; -import { ZERO_BYTES32 } from "@/scripts/utils/constants"; - export function getRoot(tree: MerkleTree): string { const root = tree.getRoot(); if (root.length == 0) { - return ZERO_BYTES32; + return ethers.ZeroHash; } return "0x" + root.toString("hex"); @@ -24,7 +22,7 @@ export function buildTree(leaves: any, hashFn: any = ethers.keccak256): MerkleTr export function buildSparseMerkleTree(leaves: any, height: number, hashFn: any = ethers.keccak256): MerkleTree { const elementsToAdd = 2 ** height - leaves.length; - const zeroHash = hashFn(ZERO_BYTES32); + const zeroHash = hashFn(ethers.ZeroHash); const zeroElements = Array(elementsToAdd).fill(zeroHash); return new MerkleTree([...leaves, ...zeroElements], hashFn, { diff --git a/test/libs/arrays/ArrayHelper.test.ts b/test/libs/arrays/ArrayHelper.test.ts index 3a70e5b0..13a85abd 100644 --- a/test/libs/arrays/ArrayHelper.test.ts +++ b/test/libs/arrays/ArrayHelper.test.ts @@ -1,8 +1,8 @@ import { ethers } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { expect } from "chai"; +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; + import { Reverter } from "@/test/helpers/reverter"; -import { ZERO_BYTES32 } from "@/scripts/utils/constants"; import { ArrayHelperMock } from "@ethers-v6"; @@ -133,9 +133,11 @@ describe("ArrayHelper", () => { it("should revert if the first index is greater than the last one", async () => { await mock.setArray((await mock.countPrefixes(array)).map((e) => Number(e))); - await expect(mock.getRangeSum(array.length - 1, 0)).to.be.revertedWith("ArrayHelper: wrong range"); - await expect(mock.getRangeSum(1, 0)).to.be.revertedWith("ArrayHelper: wrong range"); - await expect(mock.getRangeSum(2, 1)).to.be.revertedWith("ArrayHelper: wrong range"); + await expect(mock.getRangeSum(array.length - 1, 0)) + .to.be.revertedWithCustomError(mock, "InvalidRange") + .withArgs(array.length - 1, 0); + await expect(mock.getRangeSum(1, 0)).to.be.revertedWithCustomError(mock, "InvalidRange").withArgs(1, 0); + await expect(mock.getRangeSum(2, 1)).to.be.revertedWithCustomError(mock, "InvalidRange").withArgs(2, 1); }); it("should revert if one of the indexes is out of range", async () => { @@ -218,9 +220,9 @@ describe("ArrayHelper", () => { it("should reverse bytes32 array", async () => { const bytes32Arrays = [ - ZERO_BYTES32.replaceAll("0000", "1234"), - ZERO_BYTES32.replaceAll("0000", "4321"), - ZERO_BYTES32.replaceAll("0000", "abcd"), + ethers.ZeroHash.replaceAll("0000", "1234"), + ethers.ZeroHash.replaceAll("0000", "4321"), + ethers.ZeroHash.replaceAll("0000", "abcd"), ]; const arr = await mock.reverseBytes32(bytes32Arrays); @@ -293,9 +295,9 @@ describe("ArrayHelper", () => { it("should insert bytes32 array", async () => { const bytes32Arrays = [ - ZERO_BYTES32.replaceAll("0000", "1111"), - ZERO_BYTES32.replaceAll("0000", "2222"), - ZERO_BYTES32.replaceAll("0000", "3333"), + ethers.ZeroHash.replaceAll("0000", "1111"), + ethers.ZeroHash.replaceAll("0000", "2222"), + ethers.ZeroHash.replaceAll("0000", "3333"), ]; const base = [bytes32Arrays[0], bytes32Arrays[1]]; @@ -341,9 +343,9 @@ describe("ArrayHelper", () => { }); it("should crop bytes32 array properly", async () => { - let arr = await mock.cropBytes([ZERO_BYTES32, ZERO_BYTES32], 1); + let arr = await mock.cropBytes([ethers.ZeroHash, ethers.ZeroHash], 1); - expect(arr).to.deep.equal([ZERO_BYTES32]); + expect(arr).to.deep.equal([ethers.ZeroHash]); }); it("should not crop uint256 array if new length more than initial length", async () => { @@ -371,7 +373,7 @@ describe("ArrayHelper", () => { }); it("should not crop bytes32 array if new length more than initial length", async () => { - let arr = await mock.cropBytes([ZERO_BYTES32, ZERO_BYTES32], 2); + let arr = await mock.cropBytes([ethers.ZeroHash, ethers.ZeroHash], 2); expect(arr.length).to.equal(2); }); diff --git a/test/libs/arrays/SetHelper.test.ts b/test/libs/arrays/SetHelper.test.ts index 669b370c..846bd96a 100644 --- a/test/libs/arrays/SetHelper.test.ts +++ b/test/libs/arrays/SetHelper.test.ts @@ -1,10 +1,10 @@ import { ethers } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { expect } from "chai"; + +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { Reverter } from "@/test/helpers/reverter"; import { SetHelperMock } from "@ethers-v6"; -import { ZERO_BYTES32 } from "@/scripts/utils/constants"; describe("SetHelper", () => { const reverter = new Reverter(); @@ -44,9 +44,9 @@ describe("SetHelper", () => { }); it("should add to bytes32 set", async () => { - await mock.addToBytes32Set([ZERO_BYTES32]); + await mock.addToBytes32Set([ethers.ZeroHash]); - expect(await mock.getBytes32Set()).to.deep.equal([ZERO_BYTES32]); + expect(await mock.getBytes32Set()).to.deep.equal([ethers.ZeroHash]); }); it("should add to bytes set", async () => { @@ -64,31 +64,35 @@ describe("SetHelper", () => { describe("strictAdd", () => { it("should not strict add to address set if element already exists", async () => { - await expect(mock.strictAddToAddressSet([FIRST.address, FIRST.address])).to.be.revertedWith( - "SetHelper: element already exists", - ); + await expect(mock.strictAddToAddressSet([FIRST.address, FIRST.address])) + .to.be.revertedWithCustomError(mock, "ElementAlreadyExistsAddress") + .withArgs(FIRST.address); }); it("should not strict add to uint set if element already exists", async () => { - await expect(mock.strictAddToUintSet([1, 1])).to.be.revertedWith("SetHelper: element already exists"); + await expect(mock.strictAddToUintSet([1, 1])) + .to.be.revertedWithCustomError(mock, "ElementAlreadyExistsUint256") + .withArgs(1); }); it("should not strict add to bytes32 set if element already exists", async () => { - await expect(mock.strictAddToBytes32Set([ZERO_BYTES32, ZERO_BYTES32])).to.be.revertedWith( - "SetHelper: element already exists", - ); + await expect(mock.strictAddToBytes32Set([ethers.ZeroHash, ethers.ZeroHash])) + .to.be.revertedWithCustomError(mock, "ElementAlreadyExistsBytes32") + .withArgs(ethers.ZeroHash); }); it("should not strict add to bytes set if element already exists", async () => { const bytesToAdd = ethers.toUtf8Bytes("1"); - await expect(mock.strictAddToBytesSet([bytesToAdd, bytesToAdd])).to.be.revertedWith( - "SetHelper: element already exists", - ); + await expect(mock.strictAddToBytesSet([bytesToAdd, bytesToAdd])) + .to.be.revertedWithCustomError(mock, "ElementAlreadyExistsBytes") + .withArgs(bytesToAdd); }); it("should not strict add to string set if element already exists", async () => { - await expect(mock.strictAddToStringSet(["1", "1"])).to.be.revertedWith("SetHelper: element already exists"); + await expect(mock.strictAddToStringSet(["1", "1"])) + .to.be.revertedWithCustomError(mock, "ElementAlreadyExistsString") + .withArgs("1"); }); it("should strict add to address set", async () => { @@ -104,9 +108,9 @@ describe("SetHelper", () => { }); it("should strict add to bytes32 set", async () => { - await mock.strictAddToBytes32Set([ZERO_BYTES32]); + await mock.strictAddToBytes32Set([ethers.ZeroHash]); - expect(await mock.getBytes32Set()).to.deep.equal([ZERO_BYTES32]); + expect(await mock.getBytes32Set()).to.deep.equal([ethers.ZeroHash]); }); it("should strict add to bytes set", async () => { @@ -138,8 +142,8 @@ describe("SetHelper", () => { }); it("should remove from bytes32 set", async () => { - await mock.addToBytes32Set([ZERO_BYTES32]); - await mock.removeFromBytes32Set([ZERO_BYTES32]); + await mock.addToBytes32Set([ethers.ZeroHash]); + await mock.removeFromBytes32Set([ethers.ZeroHash]); expect(await mock.getBytes32Set()).to.deep.equal([]); }); @@ -166,37 +170,43 @@ describe("SetHelper", () => { it("should not strict remove from address set if no such element", async () => { await mock.strictAddToAddressSet([FIRST.address, SECOND.address]); - await expect(mock.strictRemoveFromAddressSet([SECOND.address, SECOND.address])).to.be.revertedWith( - "SetHelper: no such element", - ); + await expect(mock.strictRemoveFromAddressSet([SECOND.address, SECOND.address])) + .to.be.revertedWithCustomError(mock, "NoSuchAddress") + .withArgs(SECOND.address); }); it("should not strict remove from uint set if no such element", async () => { await mock.strictAddToUintSet([1]); - await expect(mock.strictRemoveFromUintSet([1, 1])).to.be.revertedWith("SetHelper: no such element"); + await expect(mock.strictRemoveFromUintSet([1, 1])) + .to.be.revertedWithCustomError(mock, "NoSuchUint256") + .withArgs(1); }); it("should not strict remove from bytes32 set if no such element", async () => { - await mock.strictAddToBytes32Set([ZERO_BYTES32]); + await mock.strictAddToBytes32Set([ethers.ZeroHash]); - await expect(mock.strictRemoveFromBytes32Set([ZERO_BYTES32, ZERO_BYTES32])).to.be.revertedWith( - "SetHelper: no such element", - ); + await expect(mock.strictRemoveFromBytes32Set([ethers.ZeroHash, ethers.ZeroHash])) + .to.be.revertedWithCustomError(mock, "NoSuchBytes32") + .withArgs(ethers.ZeroHash); }); it("should not strict remove from string set if no such element", async () => { await mock.strictAddToStringSet(["1", "2", "3"]); - await expect(mock.strictRemoveFromStringSet(["1", "1"])).to.be.revertedWith("SetHelper: no such element"); + await expect(mock.strictRemoveFromStringSet(["1", "1"])) + .to.be.revertedWithCustomError(mock, "NoSuchString") + .withArgs("1"); + ("SetHelper: no such element"); }); it("should not strict remove from bytes set if no such element", async () => { await mock.strictAddToBytesSet([ethers.toUtf8Bytes("1"), ethers.toUtf8Bytes("2"), ethers.toUtf8Bytes("3")]); - await expect(mock.strictRemoveFromBytesSet([ethers.toUtf8Bytes("5")])).to.be.revertedWith( - "SetHelper: no such element", - ); + const bytesToRemove = ethers.toUtf8Bytes("5"); + await expect(mock.strictRemoveFromBytesSet([bytesToRemove])) + .to.be.revertedWithCustomError(mock, "NoSuchBytes") + .withArgs(bytesToRemove); }); it("should strict remove from address set", async () => { @@ -214,8 +224,8 @@ describe("SetHelper", () => { }); it("should strict remove from bytes32 set", async () => { - await mock.strictAddToBytes32Set([ZERO_BYTES32]); - await mock.strictRemoveFromBytes32Set([ZERO_BYTES32]); + await mock.strictAddToBytes32Set([ethers.ZeroHash]); + await mock.strictRemoveFromBytes32Set([ethers.ZeroHash]); expect(await mock.getBytes32Set()).to.deep.equal([]); }); diff --git a/test/libs/crypto/ECDSA256.test.ts b/test/libs/crypto/ECDSA256.test.ts index 8354b158..c38ff1d0 100644 --- a/test/libs/crypto/ECDSA256.test.ts +++ b/test/libs/crypto/ECDSA256.test.ts @@ -50,14 +50,16 @@ describe("ECDSA256", () => { it("should revert if signature or public key has an invalid length", async () => { const wrongSig = "0x0101"; - await expect(ecdsa256.verifySECP256r1(message, wrongSig, pubKey)).to.be.revertedWith( - "ECDSA256: length is not 64", + await expect(ecdsa256.verifySECP256r1(message, wrongSig, pubKey)).to.be.revertedWithCustomError( + ecdsa256, + "LengthIsNot64", ); const wrongPubKey = "0x0101"; - await expect(ecdsa256.verifySECP256r1(message, signature, wrongPubKey)).to.be.revertedWith( - "ECDSA256: length is not 64", + await expect(ecdsa256.verifySECP256r1(message, signature, wrongPubKey)).to.be.revertedWithCustomError( + ecdsa256, + "LengthIsNot64", ); }); }); diff --git a/test/libs/data-structures/AvlTree.test.ts b/test/libs/data-structures/AvlTree.test.ts index 51847075..21c5e902 100644 --- a/test/libs/data-structures/AvlTree.test.ts +++ b/test/libs/data-structures/AvlTree.test.ts @@ -1,13 +1,12 @@ import { ethers } from "hardhat"; -import { encodeBytes32String } from "ethers"; import { expect } from "chai"; -import { Reverter } from "@/test/helpers/reverter"; import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; +import { encodeBytes32String, toBeHex } from "ethers"; -import { AvlTreeMock } from "@ethers-v6"; +import { Reverter } from "@/test/helpers/reverter"; -import { ZERO_ADDR } from "@/scripts/utils/constants"; +import { AvlTreeMock } from "@ethers-v6"; describe("AvlTree", () => { const reverter = new Reverter(); @@ -122,12 +121,16 @@ describe("AvlTree", () => { }); it("should not allow to insert 0 as key to the uint tree", async () => { - await expect(avlTree.insertUint(0, 100)).to.be.revertedWith("AvlTree: key is not allowed to be 0"); + await expect(avlTree.insertUint(0, 100)).to.be.revertedWithCustomError(avlTree, "KeyIsZero").withArgs(); }); it("should not allow to insert node with duplicate key to the uint tree", async () => { - await avlTree.insertUint(2, 10); - await expect(avlTree.insertUint(2, 4)).to.be.revertedWith("AvlTree: the node already exists"); + const key = 2; + await avlTree.insertUint(key, 10); + + await expect(avlTree.insertUint(key, 4)) + .to.be.revertedWithCustomError(avlTree, "NodeAlreadyExists") + .withArgs(toBeHex(key, 32)); }); }); @@ -220,21 +223,28 @@ describe("AvlTree", () => { it("should not allow to remove a node that doesn't exist in the uint tree", async () => { await avlTree.insertUint(1, 10); - await expect(avlTree.removeUint(2)).to.be.revertedWith("AvlTree: the node doesn't exist"); - await expect(avlTree.removeUint(0)).to.be.revertedWith("AvlTree: key is not allowed to be 0"); + await expect(avlTree.removeUint(2)) + .to.be.revertedWithCustomError(avlTree, "NodeDoesNotExist") + .withArgs(toBeHex(2, 32)); + await expect(avlTree.removeUint(0)).to.be.revertedWithCustomError(avlTree, "KeyIsZero").withArgs(); }); it("should not allow to remove a node twice in the uint tree", async () => { + const key = 2; await avlTree.insertUint(1, 10); - await avlTree.insertUint(2, 20); + await avlTree.insertUint(key, 20); - await avlTree.removeUint(2); + await avlTree.removeUint(key); - await expect(avlTree.removeUint(2)).to.be.revertedWith("AvlTree: the node doesn't exist"); + await expect(avlTree.removeUint(key)) + .to.be.revertedWithCustomError(avlTree, "NodeDoesNotExist") + .withArgs(toBeHex(key, 32)); }); it("should not allow to remove nodes in the empty uint tree", async () => { - await expect(avlTree.removeUint(1)).to.be.revertedWith("AvlTree: the node doesn't exist"); + await expect(avlTree.removeUint(1)) + .to.be.revertedWithCustomError(avlTree, "NodeDoesNotExist") + .withArgs(toBeHex(1, 32)); }); }); @@ -260,7 +270,7 @@ describe("AvlTree", () => { it("should not allow to set comparator for the uint tree if the tree is not empty", async () => { await avlTree.insertUint(1, 10); - await expect(avlTree.setUintDescComparator()).to.be.revertedWith("AvlTree: the tree must be empty"); + await expect(avlTree.setUintDescComparator()).to.be.revertedWithCustomError(avlTree, "TreeNotEmpty").withArgs(); }); }); @@ -279,23 +289,33 @@ describe("AvlTree", () => { expect(await avlTree.getUint(3)).to.be.equal(1); expect(await avlTree.tryGetUint(3)).to.deep.equal([true, 1]); - await expect(avlTree.getUint(4)).to.be.revertedWith("AvlTree: the node doesn't exist"); + await expect(avlTree.getUint(4)) + .to.be.revertedWithCustomError(avlTree, "NodeDoesNotExist") + .withArgs(toBeHex(4, 32)); expect(await avlTree.tryGetUint(4)).to.deep.equal([false, 0]); - await expect(avlTree.getUint(6)).to.be.revertedWith("AvlTree: the node doesn't exist"); + await expect(avlTree.getUint(6)) + .to.be.revertedWithCustomError(avlTree, "NodeDoesNotExist") + .withArgs(toBeHex(6, 32)); expect(await avlTree.tryGetUint(6)).to.deep.equal([false, 0]); }); it("should handle getting value for the non-existing node in the uint tree correctly", async () => { - await expect(avlTree.getUint(1)).to.be.revertedWith("AvlTree: the node doesn't exist"); + await expect(avlTree.getUint(1)) + .to.be.revertedWithCustomError(avlTree, "NodeDoesNotExist") + .withArgs(toBeHex(1, 32)); expect(await avlTree.tryGetUint(1)).to.deep.equal([false, 0]); await avlTree.insertUint(1, 30); - await expect(avlTree.getUint(2)).to.be.revertedWith("AvlTree: the node doesn't exist"); + await expect(avlTree.getUint(2)) + .to.be.revertedWithCustomError(avlTree, "NodeDoesNotExist") + .withArgs(toBeHex(2, 32)); expect(await avlTree.tryGetUint(2)).to.deep.equal([false, 0]); - await expect(avlTree.getUint(0)).to.be.revertedWith("AvlTree: the node doesn't exist"); + await expect(avlTree.getUint(0)) + .to.be.revertedWithCustomError(avlTree, "NodeDoesNotExist") + .withArgs(toBeHex(0, 32)); expect(await avlTree.tryGetUint(0)).to.deep.equal([false, 0]); }); @@ -337,7 +357,7 @@ describe("AvlTree", () => { expect(await avlTree.nextOnLast()).to.deep.equal([0, 0]); expect(await avlTree.prevOnFirst()).to.deep.equal([0, 0]); - await expect(avlTree.brokenTraversalUint()).to.be.revertedWith("Traversal: no more nodes"); + await expect(avlTree.brokenTraversalUint()).to.be.revertedWithCustomError(avlTree, "NoNodesLeft").withArgs(); }); it("should maintain idempotent traversal", async () => { @@ -400,7 +420,9 @@ describe("AvlTree", () => { expect(await avlTree.rootBytes32()).to.equal(4); expect(await avlTree.sizeBytes32()).to.equal(3); - await expect(avlTree.getBytes32(2)).to.be.revertedWith("AvlTree: the node doesn't exist"); + await expect(avlTree.getBytes32(2)) + .to.be.revertedWithCustomError(avlTree, "NodeDoesNotExist") + .withArgs(toBeHex(2, 32)); expect(await avlTree.tryGetBytes32(2)).to.deep.equal([false, 0]); let traversal = await avlTree.traverseBytes32(); @@ -420,10 +442,14 @@ describe("AvlTree", () => { expect(await avlTree.getBytes32(2)).to.be.equal(encodeBytes32String("2")); expect(await avlTree.tryGetBytes32(2)).to.deep.equal([true, encodeBytes32String("2")]); - await expect(avlTree.getBytes32(1)).to.be.revertedWith("AvlTree: the node doesn't exist"); + await expect(avlTree.getBytes32(1)) + .to.be.revertedWithCustomError(avlTree, "NodeDoesNotExist") + .withArgs(toBeHex(1, 32)); expect(await avlTree.tryGetBytes32(1)).to.deep.equal([false, encodeBytes32String("")]); - await expect(avlTree.getBytes32(3)).to.be.revertedWith("AvlTree: the node doesn't exist"); + await expect(avlTree.getBytes32(3)) + .to.be.revertedWithCustomError(avlTree, "NodeDoesNotExist") + .withArgs(toBeHex(3, 32)); expect(await avlTree.tryGetBytes32(3)).to.deep.equal([false, encodeBytes32String("")]); traversal = await avlTree.traverseBytes32(); @@ -440,7 +466,9 @@ describe("AvlTree", () => { }); it("should handle getting value in the bytes32 tree correctly", async () => { - await expect(avlTree.getBytes32(1)).to.be.revertedWith("AvlTree: the node doesn't exist"); + await expect(avlTree.getBytes32(1)) + .to.be.revertedWithCustomError(avlTree, "NodeDoesNotExist") + .withArgs(toBeHex(1, 32)); expect(await avlTree.tryGetBytes32(1)).to.deep.equal([false, encodeBytes32String("")]); await avlTree.insertBytes32(1, encodeBytes32String("")); @@ -452,10 +480,14 @@ describe("AvlTree", () => { expect(await avlTree.getBytes32(2)).to.be.equal(encodeBytes32String("2")); expect(await avlTree.tryGetBytes32(2)).to.deep.equal([true, encodeBytes32String("2")]); - await expect(avlTree.getBytes32(3)).to.be.revertedWith("AvlTree: the node doesn't exist"); + await expect(avlTree.getBytes32(3)) + .to.be.revertedWithCustomError(avlTree, "NodeDoesNotExist") + .withArgs(toBeHex(3, 32)); expect(await avlTree.tryGetBytes32(3)).to.deep.equal([false, encodeBytes32String("")]); - await expect(avlTree.getBytes32(0)).to.be.revertedWith("AvlTree: the node doesn't exist"); + await expect(avlTree.getBytes32(0)) + .to.be.revertedWithCustomError(avlTree, "NodeDoesNotExist") + .withArgs(toBeHex(0, 32)); expect(await avlTree.tryGetBytes32(0)).to.deep.equal([false, encodeBytes32String("")]); }); @@ -530,8 +562,10 @@ describe("AvlTree", () => { expect(await avlTree.rootAddress()).to.equal(5); expect(await avlTree.sizeAddress()).to.equal(3); - await expect(avlTree.getAddressValue(2)).to.be.revertedWith("AvlTree: the node doesn't exist"); - expect(await avlTree.tryGetAddress(2)).to.deep.equal([false, ZERO_ADDR]); + await expect(avlTree.getAddressValue(2)) + .to.be.revertedWithCustomError(avlTree, "NodeDoesNotExist") + .withArgs(toBeHex(2, 32)); + expect(await avlTree.tryGetAddress(2)).to.deep.equal([false, ethers.ZeroAddress]); let traversal = await avlTree.traverseAddress(); expect(traversal[0]).to.deep.equal([6, 5, 1]); @@ -553,11 +587,15 @@ describe("AvlTree", () => { expect(await avlTree.getAddressValue(1)).to.be.equal(USER2); expect(await avlTree.tryGetAddress(1)).to.deep.equal([true, USER2.address]); - await expect(avlTree.getAddressValue(3)).to.be.revertedWith("AvlTree: the node doesn't exist"); + await expect(avlTree.getAddressValue(3)) + .to.be.revertedWithCustomError(avlTree, "NodeDoesNotExist") + .withArgs(toBeHex(3, 32)); expect(await avlTree.tryGetAddress(3)).to.deep.equal([false, 0]); - await expect(avlTree.getAddressValue(5)).to.be.revertedWith("AvlTree: the node doesn't exist"); - expect(await avlTree.tryGetAddress(5)).to.deep.equal([false, ZERO_ADDR]); + await expect(avlTree.getAddressValue(5)) + .to.be.revertedWithCustomError(avlTree, "NodeDoesNotExist") + .withArgs(toBeHex(5, 32)); + expect(await avlTree.tryGetAddress(5)).to.deep.equal([false, ethers.ZeroAddress]); traversal = await avlTree.traverseAddress(); expect(traversal[0]).to.deep.equal([6, 4, 2, 1]); @@ -577,23 +615,29 @@ describe("AvlTree", () => { }); it("should handle getting value in the address tree correctly", async () => { - await expect(avlTree.getAddressValue(1)).to.be.revertedWith("AvlTree: the node doesn't exist"); - expect(await avlTree.tryGetAddress(1)).to.deep.equal([false, ZERO_ADDR]); + await expect(avlTree.getAddressValue(1)) + .to.be.revertedWithCustomError(avlTree, "NodeDoesNotExist") + .withArgs(toBeHex(1, 32)); + expect(await avlTree.tryGetAddress(1)).to.deep.equal([false, ethers.ZeroAddress]); await avlTree.insertAddress(1, USER2); - await avlTree.insertAddress(2, ZERO_ADDR); + await avlTree.insertAddress(2, ethers.ZeroAddress); expect(await avlTree.getAddressValue(1)).to.be.equal(USER2); expect(await avlTree.tryGetAddress(1)).to.deep.equal([true, USER2.address]); - expect(await avlTree.getAddressValue(2)).to.be.equal(ZERO_ADDR); - expect(await avlTree.tryGetAddress(2)).to.deep.equal([true, ZERO_ADDR]); + expect(await avlTree.getAddressValue(2)).to.be.equal(ethers.ZeroAddress); + expect(await avlTree.tryGetAddress(2)).to.deep.equal([true, ethers.ZeroAddress]); - await expect(avlTree.getAddressValue(5)).to.be.revertedWith("AvlTree: the node doesn't exist"); - expect(await avlTree.tryGetAddress(5)).to.deep.equal([false, ZERO_ADDR]); + await expect(avlTree.getAddressValue(5)) + .to.be.revertedWithCustomError(avlTree, "NodeDoesNotExist") + .withArgs(toBeHex(5, 32)); + expect(await avlTree.tryGetAddress(5)).to.deep.equal([false, ethers.ZeroAddress]); - await expect(avlTree.getAddressValue(0)).to.be.revertedWith("AvlTree: the node doesn't exist"); - expect(await avlTree.tryGetAddress(0)).to.deep.equal([false, ZERO_ADDR]); + await expect(avlTree.getAddressValue(0)) + .to.be.revertedWithCustomError(avlTree, "NodeDoesNotExist") + .withArgs(toBeHex(0, 32)); + expect(await avlTree.tryGetAddress(0)).to.deep.equal([false, ethers.ZeroAddress]); }); it("should check if custom comparator is set for the address tree correctly", async () => { diff --git a/test/libs/data-structures/CartesianMerkleTree.test.ts b/test/libs/data-structures/CartesianMerkleTree.test.ts index 3ad59d07..983d6580 100644 --- a/test/libs/data-structures/CartesianMerkleTree.test.ts +++ b/test/libs/data-structures/CartesianMerkleTree.test.ts @@ -7,10 +7,9 @@ import { CartesianMerkleTreeMock } from "@ethers-v6"; import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { Reverter } from "@/test/helpers/reverter"; -import { ZERO_BYTES32 } from "@/scripts/utils/constants"; import { getPoseidon, poseidonHash } from "@/test/helpers/poseidon-hash"; -import { CartesianMerkleTree } from "@/generated-types/ethers/contracts/mock/libs/data-structures/CartesianMerkleTreeMock"; +import { CartesianMerkleTree } from "@ethers-v6/contracts/mock/libs/data-structures/CartesianMerkleTreeMock"; describe("CartesianMerkleTree", () => { const reverter = new Reverter(); @@ -147,15 +146,15 @@ describe("CartesianMerkleTree", () => { }); it("should not initialize twice", async () => { - await expect(treaple.initializeUintTreaple(20)).to.be.rejectedWith( - "CartesianMerkleTree: treaple is already initialized", - ); + await expect(treaple.initializeUintTreaple(20)) + .to.be.revertedWithCustomError(treaple, "TreapleAlreadyInitialized") + .withArgs(); }); it("should revert if trying to set incorrect desired proof size", async () => { - await expect(treaple.setDesiredProofSizeUintTreaple(0)).to.be.rejectedWith( - "CartesianMerkleTree: desired proof size must be greater than zero", - ); + await expect(treaple.setDesiredProofSizeUintTreaple(0)) + .to.be.revertedWithCustomError(treaple, "ZeroDesiredProofSize") + .withArgs(); }); it("should correctly set new desired proof size", async () => { @@ -172,8 +171,8 @@ describe("CartesianMerkleTree", () => { }); const newTreap = await CartesianMerkleTreeMock.deploy(); - await expect(newTreap.addUint(13n)).to.be.rejectedWith("CartesianMerkleTree: treaple is not initialized"); - await expect(newTreap.removeUint(13n)).to.be.rejectedWith("CartesianMerkleTree: treaple is not initialized"); + await expect(newTreap.addUint(13n)).to.be.revertedWithCustomError(treaple, "TreapleNotInitialized").withArgs(); + await expect(newTreap.removeUint(13n)).to.be.revertedWithCustomError(treaple, "TreapleNotInitialized").withArgs(); }); it("should add and full remove elements from the CMT correctly", async () => { @@ -190,7 +189,7 @@ describe("CartesianMerkleTree", () => { await treaple.removeUint(keys[i]); } - expect(await treaple.getUintRoot()).to.equal(ZERO_BYTES32); + expect(await treaple.getUintRoot()).to.equal(ethers.ZeroHash); expect(await treaple.getUintNodesCount()).to.equal(0); @@ -275,9 +274,9 @@ describe("CartesianMerkleTree", () => { await treaple.addUint(hexKey); } - await expect(treaple.removeUint(ethers.toBeHex(8, 32))).to.be.revertedWith( - "CartesianMerkleTree: the node does not exist", - ); + await expect(treaple.removeUint(ethers.toBeHex(8, 32))) + .to.be.revertedWithCustomError(treaple, "NodeDoesNotExist") + .withArgs(); }); it("should generate empty proof on empty tree", async () => { @@ -333,10 +332,10 @@ describe("CartesianMerkleTree", () => { }); it("should revert if trying to add/remove zero key", async () => { - const reason = "CartesianMerkleTree: the key can't be zero"; + const customError = "ZeroKeyProvided"; - await expect(treaple.addUint(ZERO_BYTES32)).to.be.rejectedWith(reason); - await expect(treaple.removeUint(ZERO_BYTES32)).to.be.rejectedWith(reason); + await expect(treaple.addUint(ethers.ZeroHash)).to.be.revertedWithCustomError(treaple, customError).withArgs(); + await expect(treaple.removeUint(ethers.ZeroHash)).to.be.revertedWithCustomError(treaple, customError).withArgs(); }); it("should revert if trying to set hasher with non-empty treaple", async () => { @@ -344,7 +343,9 @@ describe("CartesianMerkleTree", () => { await treaple.addUint(key); - await expect(treaple.setUintPoseidonHasher()).to.be.rejectedWith("CartesianMerkleTree: treaple is not empty"); + await expect(treaple.setUintPoseidonHasher()) + .to.be.revertedWithCustomError(treaple, "TreapleNotEmpty") + .withArgs(); }); it("should revert if trying to add a node with the same key", async () => { @@ -352,15 +353,15 @@ describe("CartesianMerkleTree", () => { await treaple.addUint(key); - await expect(treaple.addUint(key)).to.be.rejectedWith("CartesianMerkleTree: the key already exists"); + await expect(treaple.addUint(key)).to.be.revertedWithCustomError(treaple, "KeyAlreadyExists").withArgs(); }); it("should get empty Node by non-existing key", async () => { - expect((await treaple.getUintNodeByKey(1n)).key).to.be.equal(ZERO_BYTES32); + expect((await treaple.getUintNodeByKey(1n)).key).to.be.equal(ethers.ZeroHash); await treaple.addUint(ethers.toBeHex(7n, 32)); - expect((await treaple.getUintNodeByKey(5n)).key).to.be.equal(ZERO_BYTES32); + expect((await treaple.getUintNodeByKey(5n)).key).to.be.equal(ethers.ZeroHash); }); it("should get exception if desired size is too low", async () => { @@ -371,9 +372,9 @@ describe("CartesianMerkleTree", () => { await treaple.addUint(keys[i]); } - await expect(treaple.getUintProof(keys[0], 1)).to.be.rejectedWith( - "CartesianMerkleTree: desired proof size is too low", - ); + await expect(treaple.getUintProof(keys[0], 1)) + .to.be.revertedWithCustomError(treaple, "ProofSizeTooSmall") + .withArgs(1, 1); }); }); @@ -384,9 +385,9 @@ describe("CartesianMerkleTree", () => { }); it("should not initialize twice", async () => { - await expect(treaple.initializeBytes32Treaple(20)).to.be.rejectedWith( - "CartesianMerkleTree: treaple is already initialized", - ); + await expect(treaple.initializeBytes32Treaple(20)) + .to.be.revertedWithCustomError(treaple, "TreapleAlreadyInitialized") + .withArgs(); }); it("should correctly set new desired proof size", async () => { @@ -504,9 +505,9 @@ describe("CartesianMerkleTree", () => { }); it("should not initialize twice", async () => { - await expect(treaple.initializeAddressTreaple(20)).to.be.rejectedWith( - "CartesianMerkleTree: treaple is already initialized", - ); + await expect(treaple.initializeAddressTreaple(20)) + .to.be.revertedWithCustomError(treaple, "TreapleAlreadyInitialized") + .withArgs(); }); it("should correctly set new desired proof size", async () => { diff --git a/test/libs/data-structures/DynamicSet.test.ts b/test/libs/data-structures/DynamicSet.test.ts index 96314365..d85c2508 100644 --- a/test/libs/data-structures/DynamicSet.test.ts +++ b/test/libs/data-structures/DynamicSet.test.ts @@ -1,5 +1,6 @@ import { ethers } from "hardhat"; import { expect } from "chai"; + import { Reverter } from "@/test/helpers/reverter"; import { DynamicSetMock } from "@ethers-v6"; diff --git a/test/libs/data-structures/IncrementalMerkleTree.test.ts b/test/libs/data-structures/IncrementalMerkleTree.test.ts index 8242ee07..b6ec6232 100644 --- a/test/libs/data-structures/IncrementalMerkleTree.test.ts +++ b/test/libs/data-structures/IncrementalMerkleTree.test.ts @@ -1,16 +1,15 @@ import { expect } from "chai"; import { ethers } from "hardhat"; -import { MerkleTree } from "merkletreejs"; - -import { IncrementalMerkleTreeMock } from "@ethers-v6"; - import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; +import { MerkleTree } from "merkletreejs"; import { Reverter } from "@/test/helpers/reverter"; import { getPoseidon, poseidonHash } from "@/test/helpers/poseidon-hash"; import { getRoot, buildSparseMerkleTree } from "@/test/helpers/merkle-tree-helper"; +import { IncrementalMerkleTreeMock } from "@ethers-v6"; + describe("IncrementalMerkleTree", () => { const reverter = new Reverter(); const coder = ethers.AbiCoder.defaultAbiCoder(); @@ -125,9 +124,9 @@ describe("IncrementalMerkleTree", () => { expect(await merkleTree.isUnitHashFnSet()).to.be.true; - await expect(merkleTree.setUintPoseidonHasher()).to.be.rejectedWith( - "IncrementalMerkleTree: the tree must be empty", - ); + await expect(merkleTree.setUintPoseidonHasher()) + .to.be.revertedWithCustomError(merkleTree, "TreeIsNotEmpty") + .withArgs(); }); it("should return zeroHash if tree is empty", async () => { @@ -135,9 +134,9 @@ describe("IncrementalMerkleTree", () => { }); it("should revert if an attempt is made to set the tree height lower than the current one", async () => { - await expect(merkleTree.setUintTreeHeight(0)).to.be.rejectedWith( - "IncrementalMerkleTree: the height must be greater than the current one", - ); + await expect(merkleTree.setUintTreeHeight(0)) + .to.be.revertedWithCustomError(merkleTree, "NewHeightMustBeGreater") + .withArgs(await merkleTree.getUintTreeHeight(), 0); }); it("should revert if the set tree height's limit is reached", async () => { @@ -145,7 +144,7 @@ describe("IncrementalMerkleTree", () => { await merkleTree.addUint(1); - await expect(merkleTree.addUint(2)).to.be.rejectedWith("IncrementalMerkleTree: the tree is full"); + await expect(merkleTree.addUint(2)).to.be.revertedWithCustomError(merkleTree, "TreeIsFull").withArgs(); }); }); @@ -225,9 +224,9 @@ describe("IncrementalMerkleTree", () => { expect(await merkleTree.isBytes32HashFnSet()).to.be.true; - await expect(merkleTree.setBytes32PoseidonHasher()).to.be.rejectedWith( - "IncrementalMerkleTree: the tree must be empty", - ); + await expect(merkleTree.setBytes32PoseidonHasher()) + .to.be.revertedWithCustomError(merkleTree, "TreeIsNotEmpty") + .withArgs(); }); it("should return zeroHash if tree is empty", async () => { @@ -235,9 +234,9 @@ describe("IncrementalMerkleTree", () => { }); it("should revert if an attempt is made to set the tree height lower than the current one", async () => { - await expect(merkleTree.setBytes32TreeHeight(0)).to.be.rejectedWith( - "IncrementalMerkleTree: the height must be greater than the current one", - ); + await expect(merkleTree.setBytes32TreeHeight(0)) + .to.be.revertedWithCustomError(merkleTree, "NewHeightMustBeGreater") + .withArgs(await merkleTree.getBytes32TreeHeight(), 0); }); it("should revert if the set tree height's limit is reached", async () => { @@ -245,9 +244,9 @@ describe("IncrementalMerkleTree", () => { await merkleTree.addBytes32(ethers.ZeroHash); - await expect(merkleTree.addBytes32(ethers.ZeroHash)).to.be.rejectedWith( - "IncrementalMerkleTree: the tree is full", - ); + await expect(merkleTree.addBytes32(ethers.ZeroHash)) + .to.be.revertedWithCustomError(merkleTree, "TreeIsFull") + .withArgs(); }); }); @@ -327,9 +326,9 @@ describe("IncrementalMerkleTree", () => { expect(await merkleTree.isAddressHashFnSet()).to.be.true; - await expect(merkleTree.setAddressPoseidonHasher()).to.be.rejectedWith( - "IncrementalMerkleTree: the tree must be empty", - ); + await expect(merkleTree.setAddressPoseidonHasher()) + .to.be.revertedWithCustomError(merkleTree, "TreeIsNotEmpty") + .withArgs(); }); it("should return zeroHash if tree is empty", async () => { @@ -337,9 +336,9 @@ describe("IncrementalMerkleTree", () => { }); it("should revert if an attempt is made to set the tree height lower than the current one", async () => { - await expect(merkleTree.setAddressTreeHeight(0)).to.be.rejectedWith( - "IncrementalMerkleTree: the height must be greater than the current one", - ); + await expect(merkleTree.setAddressTreeHeight(0)) + .to.be.revertedWithCustomError(merkleTree, "NewHeightMustBeGreater") + .withArgs(await merkleTree.getAddressTreeHeight(), 0); }); it("should revert if the set tree height's limit is reached", async () => { @@ -347,7 +346,9 @@ describe("IncrementalMerkleTree", () => { await merkleTree.addAddress(USER1.address); - await expect(merkleTree.addAddress(USER1.address)).to.be.rejectedWith("IncrementalMerkleTree: the tree is full"); + await expect(merkleTree.addAddress(USER1.address)) + .to.be.revertedWithCustomError(merkleTree, "TreeIsFull") + .withArgs(); }); }); }); diff --git a/test/libs/data-structures/PriorityQueue.test.ts b/test/libs/data-structures/PriorityQueue.test.ts index 8130b369..1d232450 100644 --- a/test/libs/data-structures/PriorityQueue.test.ts +++ b/test/libs/data-structures/PriorityQueue.test.ts @@ -1,5 +1,6 @@ import { ethers } from "hardhat"; import { expect } from "chai"; + import { Reverter } from "@/test/helpers/reverter"; import { PriorityQueueMock } from "@ethers-v6"; @@ -153,9 +154,9 @@ describe("PriorityQueue", () => { }); it("should not remove elements from an empty queue", async () => { - await expect(mock.topValueUint()).to.be.revertedWith("PriorityQueue: empty queue"); - await expect(mock.topUint()).to.be.revertedWith("PriorityQueue: empty queue"); - await expect(mock.removeTopUint()).to.be.revertedWith("PriorityQueue: empty queue"); + await expect(mock.topValueUint()).to.be.revertedWithCustomError(mock, "QueueIsEmpty").withArgs(); + await expect(mock.topUint()).to.be.revertedWithCustomError(mock, "QueueIsEmpty").withArgs(); + await expect(mock.removeTopUint()).to.be.revertedWithCustomError(mock, "QueueIsEmpty").withArgs(); }); }); diff --git a/test/libs/data-structures/SparseMerkleTree.test.ts b/test/libs/data-structures/SparseMerkleTree.test.ts index 666deff8..d070b9e4 100644 --- a/test/libs/data-structures/SparseMerkleTree.test.ts +++ b/test/libs/data-structures/SparseMerkleTree.test.ts @@ -1,18 +1,18 @@ -import { expect } from "chai"; import { ethers } from "hardhat"; - -import { Hash, LocalStorageDB, Merkletree, Proof, str2Bytes, verifyProof } from "@iden3/js-merkletree"; - -import { SparseMerkleTreeMock } from "@ethers-v6"; -import { SparseMerkleTree } from "@/generated-types/ethers/contracts/mock/libs/data-structures/SparseMerkleTreeMock.sol/SparseMerkleTreeMock"; +import { expect } from "chai"; import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; +import { toBeHex } from "ethers"; + +import { Hash, LocalStorageDB, Merkletree, Proof, str2Bytes, verifyProof } from "@iden3/js-merkletree"; import { Reverter } from "@/test/helpers/reverter"; -import { ZERO_BYTES32 } from "@/scripts/utils/constants"; import { getPoseidon, poseidonHash } from "@/test/helpers/poseidon-hash"; +import { SparseMerkleTreeMock } from "@ethers-v6"; + import "mock-local-storage"; +import { SparseMerkleTree } from "@/generated-types/ethers/contracts/mock/libs/data-structures/SparseMerkleTreeMock.sol/SparseMerkleTreeMock"; describe("SparseMerkleTree", () => { const reverter = new Reverter(); @@ -52,7 +52,7 @@ describe("SparseMerkleTree", () => { }); async function getRoot(tree: Merkletree): Promise { - return ethers.toBeHex((await tree.root()).bigInt(), 32); + return toBeHex((await tree.root()).bigInt(), 32); } function getOnchainProof(onchainProof: SparseMerkleTree.ProofStructOutput): Proof { @@ -83,8 +83,8 @@ describe("SparseMerkleTree", () => { async function compareNodes(node: SparseMerkleTree.NodeStructOutput, key: bigint) { const localNode = await localMerkleTree.get(key); - expect(node.key).to.equal(ethers.toBeHex(localNode.key, 32)); - expect(node.value).to.equal(ethers.toBeHex(localNode.value, 32)); + expect(node.key).to.equal(toBeHex(localNode.key, 32)); + expect(node.value).to.equal(toBeHex(localNode.value, 32)); } describe("Uint SMT", () => { @@ -94,23 +94,23 @@ describe("SparseMerkleTree", () => { }); it("should not initialize twice", async () => { - await expect(merkleTree.initializeUintTree(20)).to.be.rejectedWith( - "SparseMerkleTree: tree is already initialized", - ); + await expect(merkleTree.initializeUintTree(20)) + .to.be.revertedWithCustomError(merkleTree, "TreeAlreadyInitialized") + .withArgs(); }); it("should revert if trying to set incorrect max depth", async () => { - await expect(merkleTree.setMaxDepthUintTree(0)).to.be.rejectedWith( - "SparseMerkleTree: max depth must be greater than zero", - ); + await expect(merkleTree.setMaxDepthUintTree(0)) + .to.be.revertedWithCustomError(merkleTree, "MaxDepthIsZero") + .withArgs(); - await expect(merkleTree.setMaxDepthUintTree(15)).to.be.rejectedWith( - "SparseMerkleTree: max depth can only be increased", - ); + await expect(merkleTree.setMaxDepthUintTree(15)) + .to.be.revertedWithCustomError(merkleTree, "NewMaxDepthMustBeLarger") + .withArgs(merkleTree.getUintMaxDepth(), 15); - await expect(merkleTree.setMaxDepthUintTree(300)).to.be.rejectedWith( - "SparseMerkleTree: max depth is greater than hard cap", - ); + await expect(merkleTree.setMaxDepthUintTree(300)) + .to.be.revertedWithCustomError(merkleTree, "MaxDepthExceedsHardCap") + .withArgs(300); }); it("should set max depth bigger than the current one", async () => { @@ -128,20 +128,22 @@ describe("SparseMerkleTree", () => { }); const newMerkleTree = await SparseMerkleTreeMock.deploy(); - await expect(newMerkleTree.addUint(ethers.toBeHex(1n, 32), 1n)).to.be.rejectedWith( - "SparseMerkleTree: tree is not initialized", - ); - await expect(newMerkleTree.removeUint(ethers.toBeHex(1n, 32))).to.be.rejectedWith( - "SparseMerkleTree: tree is not initialized", - ); - await expect(newMerkleTree.updateUint(ethers.toBeHex(1n, 32), 1n)).to.be.rejectedWith( - "SparseMerkleTree: tree is not initialized", - ); + await expect(newMerkleTree.addUint(toBeHex(1n, 32), 1n)) + .to.be.revertedWithCustomError(merkleTree, "TreeNotInitialized") + .withArgs(); + + await expect(newMerkleTree.removeUint(toBeHex(1n, 32))) + .to.be.revertedWithCustomError(merkleTree, "TreeNotInitialized") + .withArgs(); + + await expect(newMerkleTree.updateUint(toBeHex(1n, 32), 1n)) + .to.be.revertedWithCustomError(merkleTree, "TreeNotInitialized") + .withArgs(); }); it("should build a Merkle Tree of a predefined size with correct initial values", async () => { const value = 2341n; - const key = poseidonHash(ethers.toBeHex(value)); + const key = poseidonHash(toBeHex(value)); expect(await merkleTree.getUintRoot()).to.equal(await getRoot(localMerkleTree)); @@ -164,8 +166,8 @@ describe("SparseMerkleTree", () => { it("should build a Merkle Tree correctly with multiple elements", async () => { for (let i = 1n; i < 20n; i++) { - const value = BigInt(ethers.toBeHex(ethers.hexlify(ethers.randomBytes(28)), 32)); - const key = poseidonHash(ethers.toBeHex(`0x` + value.toString(16), 32)); + const value = BigInt(toBeHex(ethers.hexlify(ethers.randomBytes(28)), 32)); + const key = poseidonHash(toBeHex(`0x` + value.toString(16), 32)); await merkleTree.addUint(key, value); @@ -181,15 +183,17 @@ describe("SparseMerkleTree", () => { expect(await merkleTree.isUintCustomHasherSet()).to.be.true; - await expect(merkleTree.setUintPoseidonHasher()).to.be.rejectedWith("SparseMerkleTree: tree is not empty"); + await expect(merkleTree.setUintPoseidonHasher()) + .to.be.revertedWithCustomError(merkleTree, "TreeIsNotEmpty") + .withArgs(); }); it("should add and full remove elements from Merkle Tree correctly", async () => { const keys: string[] = []; for (let i = 1n; i < 20n; i++) { - const value = BigInt(ethers.toBeHex(ethers.hexlify(ethers.randomBytes(28)), 32)); - const key = poseidonHash(ethers.toBeHex(`0x` + value.toString(16), 32)); + const value = BigInt(toBeHex(ethers.hexlify(ethers.randomBytes(28)), 32)); + const key = poseidonHash(toBeHex(`0x` + value.toString(16), 32)); await merkleTree.addUint(key, value); @@ -197,12 +201,12 @@ describe("SparseMerkleTree", () => { } for (let i = 1n; i < 20n; i++) { - const key = ethers.toBeHex(keys[Number(i) - 1], 32); + const key = toBeHex(keys[Number(i) - 1], 32); await merkleTree.removeUint(key); } - expect(await merkleTree.getUintRoot()).to.equal(ZERO_BYTES32); + expect(await merkleTree.getUintRoot()).to.equal(ethers.ZeroHash); expect(await merkleTree.getUintNodesCount()).to.equal(0); @@ -215,15 +219,15 @@ describe("SparseMerkleTree", () => { let proof; for (let i = 1n; i < 20n; i++) { - const value = BigInt(ethers.toBeHex(ethers.hexlify(ethers.randomBytes(28)), 32)); - const key = poseidonHash(ethers.toBeHex(`0x` + value.toString(16), 32)); + const value = BigInt(toBeHex(ethers.hexlify(ethers.randomBytes(28)), 32)); + const key = poseidonHash(toBeHex(`0x` + value.toString(16), 32)); await merkleTree.addUint(key, value); if (i > 1n) { await merkleTree.removeUint(key); - const hexKey = ethers.toBeHex(keys[Number(i - 2n)], 32); + const hexKey = toBeHex(keys[Number(i - 2n)], 32); expect(await merkleTree.getUintProof(hexKey)).to.deep.equal(proof); await merkleTree.addUint(key, value); @@ -235,7 +239,7 @@ describe("SparseMerkleTree", () => { } for (let key of keys) { - const hexKey = ethers.toBeHex(key, 32); + const hexKey = toBeHex(key, 32); const value = (await merkleTree.getUintNodeByKey(hexKey)).value; proof = await merkleTree.getUintProof(hexKey); @@ -252,7 +256,7 @@ describe("SparseMerkleTree", () => { const keys = [7n, 1n, 5n]; for (let key of keys) { - const hexKey = ethers.toBeHex(key, 32); + const hexKey = toBeHex(key, 32); await merkleTree.addUint(hexKey, key); } @@ -263,7 +267,7 @@ describe("SparseMerkleTree", () => { expect(await merkleTree.getUintNodesCount()).to.equal(6); for (let key of keys) { - const hexKey = ethers.toBeHex(key, 32); + const hexKey = toBeHex(key, 32); await merkleTree.removeUint(hexKey); await merkleTree.addUint(hexKey, key); @@ -277,25 +281,26 @@ describe("SparseMerkleTree", () => { const keys = [7n, 1n, 5n]; for (let key of keys) { - const hexKey = ethers.toBeHex(key, 32); + const hexKey = toBeHex(key, 32); await merkleTree.addUint(hexKey, key); } - await expect(merkleTree.removeUint(ethers.toBeHex(8, 32))).to.be.revertedWith( - "SparseMerkleTree: the node does not exist", - ); - await expect(merkleTree.removeUint(ethers.toBeHex(9, 32))).to.be.revertedWith( - "SparseMerkleTree: the leaf does not match", - ); + await expect(merkleTree.removeUint(toBeHex(8, 32))) + .to.be.revertedWithCustomError(merkleTree, "NodeDoesNotExist") + .withArgs(0); + + await expect(merkleTree.removeUint(toBeHex(9, 32))) + .to.be.revertedWithCustomError(merkleTree, "LeafDoesNotMatch") + .withArgs(toBeHex(1, 32), toBeHex(9, 32)); }); it("should update existing leaves", async () => { const keys: string[] = []; for (let i = 1n; i < 20n; i++) { - const value = BigInt(ethers.toBeHex(ethers.hexlify(ethers.randomBytes(28)), 32)); - const key = poseidonHash(ethers.toBeHex(`0x` + value.toString(16), 32)); + const value = BigInt(toBeHex(ethers.hexlify(ethers.randomBytes(28)), 32)); + const key = poseidonHash(toBeHex(`0x` + value.toString(16), 32)); await merkleTree.addUint(key, value); await localMerkleTree.add(BigInt(key), value); @@ -304,8 +309,8 @@ describe("SparseMerkleTree", () => { } for (let i = 1n; i < 20n; i++) { - const key = ethers.toBeHex(keys[Number(i) - 1], 32); - const value = BigInt(ethers.toBeHex(ethers.hexlify(ethers.randomBytes(28)), 32)); + const key = toBeHex(keys[Number(i) - 1], 32); + const value = BigInt(toBeHex(ethers.hexlify(ethers.randomBytes(28)), 32)); await merkleTree.updateUint(key, value); await localMerkleTree.update(BigInt(key), value); @@ -323,29 +328,30 @@ describe("SparseMerkleTree", () => { const keys = [7n, 1n, 5n]; for (let key of keys) { - const hexKey = ethers.toBeHex(key, 32); + const hexKey = toBeHex(key, 32); await merkleTree.addUint(hexKey, key); } - await expect(merkleTree.updateUint(ethers.toBeHex(8, 32), 1n)).to.be.revertedWith( - "SparseMerkleTree: the node does not exist", - ); - await expect(merkleTree.updateUint(ethers.toBeHex(9, 32), 1n)).to.be.revertedWith( - "SparseMerkleTree: the leaf does not match", - ); + await expect(merkleTree.updateUint(toBeHex(8, 32), 1n)) + .to.be.revertedWithCustomError(merkleTree, "NodeDoesNotExist") + .withArgs(0); + + await expect(merkleTree.updateUint(toBeHex(9, 32), 1n)) + .to.be.revertedWithCustomError(merkleTree, "LeafDoesNotMatch") + .withArgs(toBeHex(1, 32), toBeHex(9, 32)); }); it("should generate empty proof on empty tree", async () => { - const onchainProof = getOnchainProof(await merkleTree.getUintProof(ethers.toBeHex(1n, 32))); + const onchainProof = getOnchainProof(await merkleTree.getUintProof(toBeHex(1n, 32))); expect(onchainProof.allSiblings()).to.have.length(0); }); it("should generate an empty proof for but with aux fields", async () => { - await merkleTree.addUint(ethers.toBeHex(7n, 32), 1n); + await merkleTree.addUint(toBeHex(7n, 32), 1n); - const onchainProof = await merkleTree.getUintProof(ethers.toBeHex(5n, 32)); + const onchainProof = await merkleTree.getUintProof(toBeHex(5n, 32)); expect(onchainProof.auxKey).to.equal(7n); expect(onchainProof.auxValue).to.equal(1n); @@ -357,23 +363,25 @@ describe("SparseMerkleTree", () => { await localMerkleTree.add(3n, 15n); // key -> 0b011 await localMerkleTree.add(7n, 15n); // key -> 0b111 - await merkleTree.addUint(ethers.toBeHex(3n, 32), 15n); - await merkleTree.addUint(ethers.toBeHex(7n, 32), 15n); + await merkleTree.addUint(toBeHex(3n, 32), 15n); + await merkleTree.addUint(toBeHex(7n, 32), 15n); - let onchainProof = getOnchainProof(await merkleTree.getUintProof(ethers.toBeHex(5n, 32))); + let onchainProof = getOnchainProof(await merkleTree.getUintProof(toBeHex(5n, 32))); expect(await verifyProof(await localMerkleTree.root(), onchainProof, 5n, 0n)).to.be.true; - onchainProof = getOnchainProof(await merkleTree.getUintProof(ethers.toBeHex(15n, 32))); + onchainProof = getOnchainProof(await merkleTree.getUintProof(toBeHex(15n, 32))); expect(await verifyProof(await localMerkleTree.root(), onchainProof, 15n, 15n)).to.be.true; }); it("should revert if trying to add a node with the same key", async () => { const value = 2341n; - const key = poseidonHash(ethers.toBeHex(value)); + const key = poseidonHash(toBeHex(value)); await merkleTree.addUint(key, value); - await expect(merkleTree.addUint(key, value)).to.be.rejectedWith("SparseMerkleTree: the key already exists"); + await expect(merkleTree.addUint(key, value)) + .to.be.revertedWithCustomError(merkleTree, "KeyAlreadyExists") + .withArgs(key); }); it("should revert if max depth is reached", async () => { @@ -387,18 +395,18 @@ describe("SparseMerkleTree", () => { await newMerkleTree.initializeUintTree(1); - await newMerkleTree.addUint(ethers.toBeHex(1n, 32), 1n); - await newMerkleTree.addUint(ethers.toBeHex(2n, 32), 1n); + await newMerkleTree.addUint(toBeHex(1n, 32), 1n); + await newMerkleTree.addUint(toBeHex(2n, 32), 1n); - await expect(newMerkleTree.addUint(ethers.toBeHex(3n, 32), 1n)).to.be.rejectedWith( - "SparseMerkleTree: max depth reached", - ); + await expect(newMerkleTree.addUint(toBeHex(3n, 32), 1n)) + .to.be.revertedWithCustomError(merkleTree, "MaxDepthReached") + .withArgs(); }); it("should get empty Node by non-existing key", async () => { expect((await merkleTree.getUintNodeByKey(1n)).nodeType).to.be.equal(0); - await merkleTree.addUint(ethers.toBeHex(7n, 32), 1n); + await merkleTree.addUint(toBeHex(7n, 32), 1n); expect((await merkleTree.getUintNodeByKey(5n)).nodeType).to.be.equal(0); }); @@ -413,13 +421,13 @@ describe("SparseMerkleTree", () => { }); it("should not initialize twice", async () => { - await expect(merkleTree.initializeBytes32Tree(20)).to.be.rejectedWith( - "SparseMerkleTree: tree is already initialized", - ); + await expect(merkleTree.initializeBytes32Tree(20)) + .to.be.revertedWithCustomError(merkleTree, "TreeAlreadyInitialized") + .withArgs(); }); it("should build a Merkle Tree of a predefined size with correct initial values", async () => { - const value = ethers.toBeHex("0x1235", 32); + const value = toBeHex("0x1235", 32); const key = poseidonHash(value); await merkleTree.addBytes32(key, value); @@ -441,7 +449,7 @@ describe("SparseMerkleTree", () => { it("should build a Merkle Tree correctly with multiple elements", async () => { for (let i = 1n; i < 20n; i++) { - const value = ethers.toBeHex(ethers.hexlify(ethers.randomBytes(28)), 32); + const value = toBeHex(ethers.hexlify(ethers.randomBytes(28)), 32); const key = poseidonHash(value); await merkleTree.addBytes32(key, value); @@ -458,14 +466,16 @@ describe("SparseMerkleTree", () => { expect(await merkleTree.isBytes32CustomHasherSet()).to.be.true; - await expect(merkleTree.setBytes32PoseidonHasher()).to.be.rejectedWith("SparseMerkleTree: tree is not empty"); + await expect(merkleTree.setBytes32PoseidonHasher()) + .to.be.revertedWithCustomError(merkleTree, "TreeIsNotEmpty") + .withArgs(); }); it("should add and full remove elements from Merkle Tree correctly", async () => { const keys: string[] = []; for (let i = 1n; i < 20n; i++) { - const value = ethers.toBeHex(ethers.hexlify(ethers.randomBytes(28)), 32); + const value = toBeHex(ethers.hexlify(ethers.randomBytes(28)), 32); const key = poseidonHash(value); await merkleTree.addBytes32(key, value); @@ -474,12 +484,12 @@ describe("SparseMerkleTree", () => { } for (let i = 1n; i < 20n; i++) { - const key = ethers.toBeHex(keys[Number(i) - 1], 32); + const key = toBeHex(keys[Number(i) - 1], 32); await merkleTree.removeBytes32(key); } - expect(await merkleTree.getBytes32Root()).to.equal(ZERO_BYTES32); + expect(await merkleTree.getBytes32Root()).to.equal(ethers.ZeroHash); expect(await merkleTree.getBytes32NodesCount()).to.equal(0); @@ -491,7 +501,7 @@ describe("SparseMerkleTree", () => { const keys: string[] = []; for (let i = 1n; i < 20n; i++) { - const value = ethers.toBeHex(ethers.hexlify(ethers.randomBytes(28)), 32); + const value = toBeHex(ethers.hexlify(ethers.randomBytes(28)), 32); const key = poseidonHash(value); await merkleTree.addBytes32(key, value); @@ -501,10 +511,10 @@ describe("SparseMerkleTree", () => { } for (let i = 1n; i < 20n; i++) { - const key = ethers.toBeHex(keys[Number(i) - 1], 32); - const value = BigInt(ethers.toBeHex(ethers.hexlify(ethers.randomBytes(28)), 32)); + const key = toBeHex(keys[Number(i) - 1], 32); + const value = BigInt(toBeHex(ethers.hexlify(ethers.randomBytes(28)), 32)); - await merkleTree.updateBytes32(key, ethers.toBeHex(value, 32)); + await merkleTree.updateBytes32(key, toBeHex(value, 32)); await localMerkleTree.update(BigInt(key), BigInt(value)); expect(await merkleTree.getBytes32Root()).to.equal(await getRoot(localMerkleTree)); @@ -526,9 +536,9 @@ describe("SparseMerkleTree", () => { }); it("should not initialize twice", async () => { - await expect(merkleTree.initializeAddressTree(20)).to.be.rejectedWith( - "SparseMerkleTree: tree is already initialized", - ); + await expect(merkleTree.initializeAddressTree(20)) + .to.be.revertedWithCustomError(merkleTree, "TreeAlreadyInitialized") + .withArgs(); }); it("should build a Merkle Tree of a predefined size with correct initial values", async () => { @@ -554,7 +564,7 @@ describe("SparseMerkleTree", () => { it("should build a Merkle Tree correctly with multiple elements", async () => { for (let i = 1n; i < 20n; i++) { - const value = ethers.toBeHex(BigInt(await USER1.getAddress()) + i); + const value = toBeHex(BigInt(await USER1.getAddress()) + i); const key = poseidonHash(value); await merkleTree.addAddress(key, value); @@ -571,14 +581,16 @@ describe("SparseMerkleTree", () => { expect(await merkleTree.isAddressCustomHasherSet()).to.be.true; - await expect(merkleTree.setAddressPoseidonHasher()).to.be.rejectedWith("SparseMerkleTree: tree is not empty"); + await expect(merkleTree.setAddressPoseidonHasher()) + .to.be.revertedWithCustomError(merkleTree, "TreeIsNotEmpty") + .withArgs(); }); it("should add and full remove elements from Merkle Tree correctly", async () => { const keys: string[] = []; for (let i = 1n; i < 20n; i++) { - const value = ethers.toBeHex(BigInt(await USER1.getAddress()) + i); + const value = toBeHex(BigInt(await USER1.getAddress()) + i); const key = poseidonHash(value); await merkleTree.addAddress(key, value); @@ -587,12 +599,12 @@ describe("SparseMerkleTree", () => { } for (let i = 1n; i < 20n; i++) { - const key = ethers.toBeHex(keys[Number(i) - 1], 32); + const key = toBeHex(keys[Number(i) - 1], 32); await merkleTree.removeAddress(key); } - expect(await merkleTree.getAddressRoot()).to.equal(ZERO_BYTES32); + expect(await merkleTree.getAddressRoot()).to.equal(ethers.ZeroHash); expect(await merkleTree.getAddressNodesCount()).to.equal(0); @@ -604,7 +616,7 @@ describe("SparseMerkleTree", () => { const keys: string[] = []; for (let i = 1n; i < 20n; i++) { - const value = ethers.toBeHex(BigInt(await USER1.getAddress()) + i); + const value = toBeHex(BigInt(await USER1.getAddress()) + i); const key = poseidonHash(value); await merkleTree.addAddress(key, value); @@ -614,10 +626,10 @@ describe("SparseMerkleTree", () => { } for (let i = 1n; i < 20n; i++) { - const key = ethers.toBeHex(keys[Number(i) - 1], 32); - const value = ethers.toBeHex(ethers.hexlify(ethers.randomBytes(20))); + const key = toBeHex(keys[Number(i) - 1], 32); + const value = toBeHex(ethers.hexlify(ethers.randomBytes(20))); - await merkleTree.updateAddress(key, ethers.toBeHex(value, 20)); + await merkleTree.updateAddress(key, toBeHex(value, 20)); await localMerkleTree.update(BigInt(key), BigInt(value)); expect(await merkleTree.getAddressRoot()).to.equal(await getRoot(localMerkleTree)); diff --git a/test/libs/data-structures/memory/Vector.test.ts b/test/libs/data-structures/memory/Vector.test.ts index 6c474356..c849f598 100644 --- a/test/libs/data-structures/memory/Vector.test.ts +++ b/test/libs/data-structures/memory/Vector.test.ts @@ -1,5 +1,6 @@ import { ethers } from "hardhat"; import { expect } from "chai"; + import { Reverter } from "@/test/helpers/reverter"; import { VectorMock } from "@ethers-v6"; @@ -40,9 +41,9 @@ describe("Vector", () => { }); it("should test empty vector", async () => { - await expect(vector.testEmptyPop()).to.be.revertedWith("Vector: empty vector"); - await expect(vector.testEmptySet()).to.be.revertedWith("Vector: out of bounds"); - await expect(vector.testEmptyAt()).to.be.revertedWith("Vector: out of bounds"); + await expect(vector.testEmptyPop()).to.be.revertedWithCustomError(vector, "PopEmptyVector").withArgs(); + await expect(vector.testEmptySet()).to.be.revertedWithCustomError(vector, "IndexOutOfBounds").withArgs(1, 0); + await expect(vector.testEmptyAt()).to.be.revertedWithCustomError(vector, "IndexOutOfBounds").withArgs(0, 0); }); }); diff --git a/test/libs/utils/DecimalsConverter.test.ts b/test/libs/utils/DecimalsConverter.test.ts index e474217a..45aab9f3 100644 --- a/test/libs/utils/DecimalsConverter.test.ts +++ b/test/libs/utils/DecimalsConverter.test.ts @@ -1,5 +1,6 @@ import { ethers } from "hardhat"; import { expect } from "chai"; + import { Reverter } from "@/test/helpers/reverter"; import { wei } from "@/scripts/utils/utils"; @@ -95,7 +96,9 @@ describe("DecimalsConverter", () => { }); it("should get exception if the result of conversion is zero", async () => { - await expect(mock.to18Safe(wei("1", 11), 30)).to.be.revertedWith("DecimalsConverter: conversion failed"); + await expect(mock.to18Safe(wei("1", 11), 30)) + .to.be.revertedWithCustomError(mock, "ConversionFailed") + .withArgs(); }); }); @@ -142,7 +145,9 @@ describe("DecimalsConverter", () => { }); it("should get exception if the result of conversion is zero", async () => { - await expect(mock.from18Safe(wei("1", 6), 6)).to.be.revertedWith("DecimalsConverter: conversion failed"); + await expect(mock.from18Safe(wei("1", 6), 6)) + .to.be.revertedWithCustomError(mock, "ConversionFailed") + .withArgs(); }); }); @@ -180,7 +185,9 @@ describe("DecimalsConverter", () => { }); it("should get exception if the result of conversion is zero", async () => { - await expect(mock.round18Safe(wei("1", 6), 6)).to.be.revertedWith("DecimalsConverter: conversion failed"); + await expect(mock.round18Safe(wei("1", 6), 6)) + .to.be.revertedWithCustomError(mock, "ConversionFailed") + .withArgs(); }); }); }); diff --git a/test/libs/utils/ReturnDataProxy.test.ts b/test/libs/utils/ReturnDataProxy.test.ts index f33ea84f..cf5c1ad1 100644 --- a/test/libs/utils/ReturnDataProxy.test.ts +++ b/test/libs/utils/ReturnDataProxy.test.ts @@ -1,5 +1,6 @@ import { ethers } from "hardhat"; import { expect } from "chai"; + import { Reverter } from "@/test/helpers/reverter"; import { wei } from "@/scripts/utils/utils"; @@ -38,7 +39,7 @@ describe("ReturnDataProxy", () => { }); it("should revert with message", async () => { - await expect(proxyMock.callRevertWithMessage()).to.be.revertedWith("test"); + await expect(proxyMock.callRevertWithMessage()).to.be.revertedWithCustomError(rawMock, "Test").withArgs(); }); }); @@ -51,7 +52,7 @@ describe("ReturnDataProxy", () => { }); it("should return revert message", async () => { - await expect(proxyMock.staticCallRevertWithMessage()).to.be.revertedWith("test"); + await expect(proxyMock.staticCallRevertWithMessage()).to.be.revertedWithCustomError(rawMock, "Test").withArgs(); }); it("should return value with passed arguments", async () => { @@ -73,7 +74,7 @@ describe("ReturnDataProxy", () => { }); it("should revert with message", async () => { - await expect(proxyMock.delegateCallRevertWithMessage()).to.be.revertedWith("test"); + await expect(proxyMock.delegateCallRevertWithMessage()).to.be.revertedWithCustomError(rawMock, "Test").withArgs(); }); }); }); diff --git a/test/libs/utils/TypeCaster.test.ts b/test/libs/utils/TypeCaster.test.ts index efbadc1d..536fa770 100644 --- a/test/libs/utils/TypeCaster.test.ts +++ b/test/libs/utils/TypeCaster.test.ts @@ -1,7 +1,8 @@ import { ethers } from "hardhat"; import { expect } from "chai"; + import { Reverter } from "@/test/helpers/reverter"; -import { ZERO_ADDR, ETHER_ADDR, ZERO_BYTES32 } from "@/scripts/utils/constants"; +import { ETHER_ADDR } from "@/scripts/utils/constants"; import { TypeCasterMock } from "@ethers-v6"; @@ -21,7 +22,7 @@ describe("TypeCaster", () => { afterEach(reverter.revert); describe("array cast", () => { - const addressArrays = [[], [ZERO_ADDR, ETHER_ADDR]]; + const addressArrays = [[], [ethers.ZeroAddress, ETHER_ADDR]]; const bytes32Arrays = [[], addressArrays[1].flatMap((e) => coder.encode(["address"], [e]))]; const uint256Arrays = [[], (bytes32Arrays[1].flatMap((e) => coder.decode(["uint256"], e)))]; @@ -69,7 +70,7 @@ describe("TypeCaster", () => { }); describe("singleton array", () => { - const MOCKED_BYTES32 = ZERO_BYTES32.replaceAll("0000", "1234"); + const MOCKED_BYTES32 = ethers.ZeroHash.replaceAll("0000", "1234"); it("should build singleton arrays properly", async () => { expect(await mock.asSingletonArrayFromUint256(123)).to.deep.equal([123n]); diff --git a/test/libs/zkp/VerifierHelper.test.ts b/test/libs/zkp/Groth16VerifierHelper.test.ts similarity index 56% rename from test/libs/zkp/VerifierHelper.test.ts rename to test/libs/zkp/Groth16VerifierHelper.test.ts index 7c2cdbf4..bc82c90d 100644 --- a/test/libs/zkp/VerifierHelper.test.ts +++ b/test/libs/zkp/Groth16VerifierHelper.test.ts @@ -1,15 +1,16 @@ import { ethers } from "hardhat"; import { expect } from "chai"; + import { Reverter } from "@/test/helpers/reverter"; -import { VerifierHelperMock, Verifier2Mock, Verifier3Mock } from "@ethers-v6"; +import { Groth16VerifierHelperMock, Groth16Verifier2Mock, Groth16Verifier3Mock } from "@ethers-v6"; -describe("VerifierHelper", () => { +describe("Groth16VerifierHelper", () => { const reverter = new Reverter(); - let verifierHelper: VerifierHelperMock; - let verifier2: Verifier2Mock; - let verifier3: Verifier3Mock; + let verifierHelper: Groth16VerifierHelperMock; + let verifier2: Groth16Verifier2Mock; + let verifier3: Groth16Verifier3Mock; const a = <[number, number]>[10, 20]; const b = <[[number, number], [number, number]]>[ @@ -22,14 +23,14 @@ describe("VerifierHelper", () => { const pubSignals3 = [3, 6, 9]; before("setup", async () => { - const VerifierHelperMock = await ethers.getContractFactory("VerifierHelperMock"); - const Verifier2Mock = await ethers.getContractFactory("Verifier2Mock"); - const Verifier3Mock = await ethers.getContractFactory("Verifier3Mock"); + const Groth16VerifierHelperMock = await ethers.getContractFactory("Groth16VerifierHelperMock"); + const Groth16Verifier2Mock = await ethers.getContractFactory("Groth16Verifier2Mock"); + const Groth16Verifier3Mock = await ethers.getContractFactory("Groth16Verifier3Mock"); - verifierHelper = await VerifierHelperMock.deploy(); + verifierHelper = await Groth16VerifierHelperMock.deploy(); - verifier2 = await Verifier2Mock.deploy(true, pubSignals2); - verifier3 = await Verifier3Mock.deploy(true, pubSignals3); + verifier2 = await Groth16Verifier2Mock.deploy(true, pubSignals2); + verifier3 = await Groth16Verifier3Mock.deploy(true, pubSignals3); await reverter.snapshot(); }); @@ -54,12 +55,12 @@ describe("VerifierHelper", () => { it("should get exception if failed to call verifyProof function", async () => { const wrongPubSignals = [1, 1, 2, 3]; - await expect( - verifierHelper.verifyProofStruct(await verifier2.getAddress(), wrongPubSignals, { a, b, c }), - ).to.be.revertedWith("VerifierHelper: failed to call verifyProof function"); - await expect( - verifierHelper.verifyProof(await verifier3.getAddress(), wrongPubSignals, a, b, c), - ).to.be.revertedWith("VerifierHelper: failed to call verifyProof function"); + await expect(verifierHelper.verifyProofStruct(await verifier2.getAddress(), wrongPubSignals, { a, b, c })) + .to.be.revertedWithCustomError(verifierHelper, "FailedToCallVerifyProof") + .withArgs(); + await expect(verifierHelper.verifyProof(await verifier3.getAddress(), wrongPubSignals, a, b, c)) + .to.be.revertedWithCustomError(verifierHelper, "FailedToCallVerifyProof") + .withArgs(); }); }); @@ -77,12 +78,13 @@ describe("VerifierHelper", () => { }); it("should get an exception if it passes invalid public signals arr", async () => { - await expect( - verifierHelper.verifyProofStructSafe(await verifier2.getAddress(), pubSignals2, { a, b, c }, 4), - ).to.be.revertedWith("VerifierHelper: invalid public signals count"); - await expect( - verifierHelper.verifyProofSafe(await verifier3.getAddress(), pubSignals3, a, b, c, 4), - ).to.be.revertedWith("VerifierHelper: invalid public signals count"); + await expect(verifierHelper.verifyProofStructSafe(await verifier2.getAddress(), pubSignals2, { a, b, c }, 4)) + .to.be.revertedWithCustomError(verifierHelper, "InvalidPublicSignalsCount") + .withArgs(pubSignals2.length, 4); + + await expect(verifierHelper.verifyProofSafe(await verifier3.getAddress(), pubSignals3, a, b, c, 4)) + .to.be.revertedWithCustomError(verifierHelper, "InvalidPublicSignalsCount") + .withArgs(pubSignals3.length, 4); }); }); }); diff --git a/test/oracles/UniswapV2Oracle.test.ts b/test/oracles/UniswapV2Oracle.test.ts index f9682953..4442b498 100644 --- a/test/oracles/UniswapV2Oracle.test.ts +++ b/test/oracles/UniswapV2Oracle.test.ts @@ -1,6 +1,7 @@ import { ethers } from "hardhat"; -import { time } from "@nomicfoundation/hardhat-network-helpers"; import { expect } from "chai"; +import { time } from "@nomicfoundation/hardhat-network-helpers"; + import { wei } from "@/scripts/utils/utils"; import { Reverter } from "@/test/helpers/reverter"; @@ -52,12 +53,13 @@ describe("UniswapV2Oracle", () => { }); it("should not initialize twice", async () => { - await expect(oracle.mockInit(await uniswapV2Factory.getAddress(), ORACLE_TIME_WINDOW)).to.be.revertedWith( - "Initializable: contract is not initializing", - ); - await expect( - oracle.__OracleV2Mock_init(await uniswapV2Factory.getAddress(), ORACLE_TIME_WINDOW), - ).to.be.revertedWith("Initializable: contract is already initialized"); + await expect(oracle.mockInit(await uniswapV2Factory.getAddress(), ORACLE_TIME_WINDOW)) + .to.be.revertedWithCustomError(oracle, "NotInitializing") + .withArgs(); + + await expect(oracle.__OracleV2Mock_init(await uniswapV2Factory.getAddress(), ORACLE_TIME_WINDOW)) + .to.be.revertedWithCustomError(oracle, "InvalidInitialization") + .withArgs(); }); }); @@ -69,7 +71,7 @@ describe("UniswapV2Oracle", () => { }); it("shouldn't set 0 timewindow", async () => { - await expect(oracle.setTimeWindow(0)).to.be.revertedWith("UniswapV2Oracle: time window can't be 0"); + await expect(oracle.setTimeWindow(0)).to.be.revertedWithCustomError(oracle, "TimeWindowIsZero").withArgs(); }); it("should add paths correctly", async () => { @@ -84,19 +86,23 @@ describe("UniswapV2Oracle", () => { }); it("should not allow to set path with length < 2", async () => { - await expect(oracle.addPaths([[C_TOKEN]])).to.be.revertedWith("UniswapV2Oracle: path must be longer than 2"); + await expect(oracle.addPaths([[C_TOKEN]])) + .to.be.revertedWithCustomError(oracle, "InvalidPath") + .withArgs(C_TOKEN, 1); }); it("should not allow to set path with non-existent pairs", async () => { - await expect(oracle.addPaths([A_C_PATH])).to.be.revertedWith("UniswapV2Oracle: uniswap pair doesn't exist"); + await expect(oracle.addPaths([A_C_PATH])) + .to.be.revertedWithCustomError(oracle, "PairDoesNotExist") + .withArgs(A_TOKEN, C_TOKEN); }); it("should not add same path twice", async () => { await createPairs(); - await expect(oracle.addPaths([A_C_PATH, A_C_PATH])).to.be.revertedWith( - "UniswapV2Oracle: path already registered", - ); + await expect(oracle.addPaths([A_C_PATH, A_C_PATH])) + .to.be.revertedWithCustomError(oracle, "PathAlreadyRegistered") + .withArgs(A_TOKEN); }); }); @@ -208,7 +214,9 @@ describe("UniswapV2Oracle", () => { }); it("should not get price if there is no path", async () => { - await expect(oracle.getPrice(A_TOKEN, 10)).to.be.revertedWith("UniswapV2Oracle: invalid path"); + await expect(oracle.getPrice(A_TOKEN, 10)) + .to.be.revertedWithCustomError(oracle, "InvalidPath") + .withArgs(A_TOKEN, 0); }); }); }); diff --git a/test/oracles/UniswapV3Oracle.test.ts b/test/oracles/UniswapV3Oracle.test.ts index 86b494f9..42097b28 100644 --- a/test/oracles/UniswapV3Oracle.test.ts +++ b/test/oracles/UniswapV3Oracle.test.ts @@ -1,8 +1,10 @@ import { ethers } from "hardhat"; -import { time } from "@nomicfoundation/hardhat-network-helpers"; import { expect } from "chai"; + +import { time } from "@nomicfoundation/hardhat-network-helpers"; import { BigNumberish } from "ethers"; import { BigNumber } from "bignumber.js"; + import { wei } from "@/scripts/utils/utils"; import { Reverter } from "@/test/helpers/reverter"; diff --git a/test/proxy/beacon/ProxyBeacon.test.ts b/test/proxy/beacon/ProxyBeacon.test.ts index a1e260ab..addf8f8b 100644 --- a/test/proxy/beacon/ProxyBeacon.test.ts +++ b/test/proxy/beacon/ProxyBeacon.test.ts @@ -1,8 +1,9 @@ import { ethers } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { expect } from "chai"; + +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; + import { Reverter } from "@/test/helpers/reverter"; -import { ZERO_ADDR } from "@/scripts/utils/constants"; import { ProxyBeacon, ERC20Mock } from "@ethers-v6"; @@ -31,7 +32,7 @@ describe("ProxyBeacon", () => { describe("functions", () => { it("should upgrade", async () => { - expect(await proxyBeacon.implementation()).to.equal(ZERO_ADDR); + expect(await proxyBeacon.implementation()).to.equal(ethers.ZeroAddress); await proxyBeacon.upgradeTo(await token.getAddress()); @@ -39,13 +40,15 @@ describe("ProxyBeacon", () => { }); it("should not upgrade to non-contract", async () => { - await expect(proxyBeacon.upgradeTo(SECOND)).to.be.revertedWith("ProxyBeacon: not a contract"); + await expect(proxyBeacon.upgradeTo(SECOND)) + .to.be.revertedWithCustomError(proxyBeacon, "NewImplementationNotAContract") + .withArgs(SECOND); }); it("only owner should upgrade", async () => { - await expect(proxyBeacon.connect(SECOND).upgradeTo(await token.getAddress())).to.be.revertedWith( - "PermanentOwnable: caller is not the owner", - ); + await expect(proxyBeacon.connect(SECOND).upgradeTo(await token.getAddress())) + .to.be.revertedWithCustomError(proxyBeacon, "UnauthorizedAccount") + .withArgs(SECOND); }); }); }); diff --git a/test/proxy/beacon/PublicBeaconProxy.test.ts b/test/proxy/beacon/PublicBeaconProxy.test.ts index 33f263ce..701197df 100644 --- a/test/proxy/beacon/PublicBeaconProxy.test.ts +++ b/test/proxy/beacon/PublicBeaconProxy.test.ts @@ -1,6 +1,7 @@ import { ethers } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { expect } from "chai"; +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; + import { Reverter } from "@/test/helpers/reverter"; import { PublicBeaconProxy, ProxyBeacon, ERC20Mock } from "@ethers-v6"; diff --git a/test/proxy/transparent/AdminableProxy.test.ts b/test/proxy/transparent/AdminableProxy.test.ts new file mode 100644 index 00000000..e3112ce0 --- /dev/null +++ b/test/proxy/transparent/AdminableProxy.test.ts @@ -0,0 +1,58 @@ +import { ethers } from "hardhat"; +import { expect } from "chai"; + +import { impersonateAccount, setBalance } from "@nomicfoundation/hardhat-network-helpers"; +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; + +import { Reverter } from "@/test/helpers/reverter"; + +import { AdminableProxyUpgrader, AdminableProxy, ERC20Mock } from "@ethers-v6"; + +describe("AdminableProxy", () => { + const reverter = new Reverter(); + + let OWNER: SignerWithAddress; + let PROXY_UPGRADER: SignerWithAddress; + + let proxy: AdminableProxy; + let tokenProxy: ERC20Mock; + + before("setup", async () => { + [OWNER] = await ethers.getSigners(); + + const ERC20Mock = await ethers.getContractFactory("ERC20Mock"); + const AdminableProxyUpgrader = await ethers.getContractFactory("AdminableProxyUpgrader"); + const AdminableProxy = await ethers.getContractFactory("AdminableProxy"); + + const token: ERC20Mock = await ERC20Mock.deploy("mock", "mock", 18); + + const adminableProxyUpgrader: AdminableProxyUpgrader = await AdminableProxyUpgrader.deploy(); + proxy = await AdminableProxy.deploy(await token.getAddress(), await adminableProxyUpgrader.getAddress(), "0x"); + + tokenProxy = token.attach(await proxy.getAddress()); + + await impersonateAccount(await adminableProxyUpgrader.getAddress()); + PROXY_UPGRADER = await ethers.provider.getSigner(await adminableProxyUpgrader.getAddress()); + await setBalance(await PROXY_UPGRADER.getAddress(), ethers.parseEther("1")); + + await reverter.snapshot(); + }); + + afterEach(reverter.revert); + + describe("delegated functions", () => { + const AMOUNT = 10; + + it("proxy admin cannot call delegated functions", async () => { + await expect(tokenProxy.connect(PROXY_UPGRADER).mint(OWNER.address, AMOUNT)) + .to.be.revertedWithCustomError(proxy, "ProxyDeniedAdminAccess") + .withArgs(); + }); + + it("everyone except proxy admin can call delegated functions", async () => { + await tokenProxy.mint(OWNER.address, AMOUNT); + + expect(await tokenProxy.balanceOf(OWNER.address)).to.equal(AMOUNT); + }); + }); +}); diff --git a/test/proxy/transparent/AdminableProxyUpgrader.test.ts b/test/proxy/transparent/AdminableProxyUpgrader.test.ts new file mode 100644 index 00000000..9d5e98e1 --- /dev/null +++ b/test/proxy/transparent/AdminableProxyUpgrader.test.ts @@ -0,0 +1,53 @@ +import { ethers } from "hardhat"; +import { expect } from "chai"; +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; + +import { Reverter } from "@/test/helpers/reverter"; + +import { AdminableProxyUpgrader, AdminableProxy, ERC20Mock } from "@ethers-v6"; + +describe("AdminableProxyUpgrader", () => { + const reverter = new Reverter(); + + let OWNER: SignerWithAddress; + let SECOND: SignerWithAddress; + + let adminableProxyUpgrader: AdminableProxyUpgrader; + let token: ERC20Mock; + let proxy: AdminableProxy; + + before("setup", async () => { + [OWNER, SECOND] = await ethers.getSigners(); + + const ERC20Mock = await ethers.getContractFactory("ERC20Mock"); + const AdminableProxyUpgrader = await ethers.getContractFactory("AdminableProxyUpgrader"); + const AdminableProxy = await ethers.getContractFactory("AdminableProxy"); + + token = await ERC20Mock.deploy("mock", "mock", 18); + + adminableProxyUpgrader = await AdminableProxyUpgrader.deploy(); + proxy = await AdminableProxy.deploy(await token.getAddress(), await adminableProxyUpgrader.getAddress(), "0x"); + + await reverter.snapshot(); + }); + + afterEach(reverter.revert); + + describe("upgrade", () => { + it("only owner should upgrade", async () => { + await expect( + adminableProxyUpgrader.connect(SECOND).upgrade(await proxy.getAddress(), await proxy.getAddress(), "0x"), + ) + .to.be.revertedWithCustomError(adminableProxyUpgrader, "UnauthorizedAccount") + .withArgs(SECOND); + }); + }); + + describe("getImplementation", () => { + it("should get implementation", async () => { + expect(await adminableProxyUpgrader.getImplementation(await proxy.getAddress())).to.equal( + await token.getAddress(), + ); + }); + }); +}); diff --git a/test/proxy/transparent/TransparentProxyUpgrader.test.ts b/test/proxy/transparent/TransparentProxyUpgrader.test.ts deleted file mode 100644 index 6b021e42..00000000 --- a/test/proxy/transparent/TransparentProxyUpgrader.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { ethers } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; -import { expect } from "chai"; -import { Reverter } from "@/test/helpers/reverter"; - -import { TransparentProxyUpgrader, TransparentUpgradeableProxy, ERC20Mock } from "@ethers-v6"; - -describe("TransparentProxyUpgrader", () => { - const reverter = new Reverter(); - - let OWNER: SignerWithAddress; - let SECOND: SignerWithAddress; - - let transparentProxyUpgrader: TransparentProxyUpgrader; - let token: ERC20Mock; - let proxy: TransparentUpgradeableProxy; - - before("setup", async () => { - [OWNER, SECOND] = await ethers.getSigners(); - - const ERC20Mock = await ethers.getContractFactory("ERC20Mock"); - const TransparentProxyUpgrader = await ethers.getContractFactory("TransparentProxyUpgrader"); - const TransparentUpgradeableProxy = await ethers.getContractFactory("TransparentUpgradeableProxy"); - - token = await ERC20Mock.deploy("mock", "mock", 18); - - transparentProxyUpgrader = await TransparentProxyUpgrader.deploy(); - proxy = await TransparentUpgradeableProxy.deploy( - await token.getAddress(), - await transparentProxyUpgrader.getAddress(), - "0x", - ); - - await reverter.snapshot(); - }); - - afterEach(reverter.revert); - - describe("upgrade", () => { - it("only owner should upgrade", async () => { - await expect( - transparentProxyUpgrader.connect(SECOND).upgrade(await proxy.getAddress(), await proxy.getAddress(), "0x"), - ).to.be.revertedWith("PermanentOwnable: caller is not the owner"); - }); - }); - - describe("getImplementation", () => { - it("should get implementation", async () => { - expect(await transparentProxyUpgrader.getImplementation(await proxy.getAddress())).to.equal( - await token.getAddress(), - ); - }); - - it("should not get implementation", async () => { - await expect(transparentProxyUpgrader.getImplementation(await token.getAddress())).to.be.revertedWith( - "TransparentProxyUpgrader: not a proxy", - ); - }); - }); -}); diff --git a/test/token/SBT.test.ts b/test/token/SBT.test.ts index 8dd3a71a..eb6a392d 100644 --- a/test/token/SBT.test.ts +++ b/test/token/SBT.test.ts @@ -1,8 +1,9 @@ import { ethers } from "hardhat"; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { expect } from "chai"; + +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; + import { Reverter } from "@/test/helpers/reverter"; -import { ZERO_ADDR } from "@/scripts/utils/constants"; import { SBTMock } from "@ethers-v6"; @@ -31,10 +32,10 @@ describe("SBT", () => { describe("access", () => { it("should not initialize twice", async () => { - await expect(sbt.__SBTMock_init(name, symbol)).to.be.revertedWith( - "Initializable: contract is already initialized", - ); - await expect(sbt.mockInit(name, symbol)).to.be.revertedWith("Initializable: contract is not initializing"); + await expect(sbt.__SBTMock_init(name, symbol)) + .to.be.revertedWithCustomError(sbt, "InvalidInitialization") + .withArgs(); + await expect(sbt.mockInit(name, symbol)).to.be.revertedWithCustomError(sbt, "NotInitializing").withArgs(); }); }); @@ -59,17 +60,19 @@ describe("SBT", () => { expect(await sbt.tokenURI(1337)).to.equal(""); - expect(tx).to.emit(sbt, "Transfer").withArgs(ZERO_ADDR, FIRST.address, 1337); + expect(tx).to.emit(sbt, "Transfer").withArgs(ethers.ZeroAddress, FIRST.address, 1337); }); it("should not mint to null address", async () => { - await expect(sbt.mint(ZERO_ADDR, 1)).to.be.revertedWith("SBT: address(0) receiver"); + await expect(sbt.mint(ethers.ZeroAddress, 1)) + .to.be.revertedWithCustomError(sbt, "ReceiverIsZeroAddress") + .withArgs(); }); it("should not mint token twice", async () => { await sbt.mint(FIRST.address, 1); - await expect(sbt.mint(FIRST.address, 1)).to.be.revertedWith("SBT: token already exists"); + await expect(sbt.mint(FIRST.address, 1)).to.be.revertedWithCustomError(sbt, "TokenAlreadyExists").withArgs(1); }); }); @@ -82,15 +85,15 @@ describe("SBT", () => { expect(await sbt.tokenExists(0)).to.be.false; expect(await sbt.balanceOf(FIRST.address)).to.equal(0n); - expect(await sbt.ownerOf(0)).to.equal(ZERO_ADDR); + expect(await sbt.ownerOf(0)).to.equal(ethers.ZeroAddress); expect(await sbt.tokensOf(FIRST.address)).to.deep.equal([]); - expect(tx).to.emit(sbt, "Transfer").withArgs(FIRST.address, ZERO_ADDR, 1337); + expect(tx).to.emit(sbt, "Transfer").withArgs(FIRST.address, ethers.ZeroAddress, 1337); }); it("should not burn SBT that doesn't exist", async () => { - await expect(sbt.burn(1337)).to.be.revertedWith("SBT: token doesn't exist"); + await expect(sbt.burn(1337)).to.be.revertedWithCustomError(sbt, "TokenDoesNotExist").withArgs(1337); }); }); @@ -103,7 +106,7 @@ describe("SBT", () => { }); it("should not set uri for non-existent token", async () => { - await expect(sbt.setTokenURI(1337, "")).to.be.revertedWith("SBT: token doesn't exist"); + await expect(sbt.setTokenURI(1337, "")).to.be.revertedWithCustomError(sbt, "TokenDoesNotExist").withArgs(1337); }); it("should reset token URI if token is burnder", async () => { diff --git a/test/utils/BlockGuard.test.ts b/test/utils/BlockGuard.test.ts index 380c79f7..8905913c 100644 --- a/test/utils/BlockGuard.test.ts +++ b/test/utils/BlockGuard.test.ts @@ -1,7 +1,9 @@ import { ethers } from "hardhat"; +import { expect } from "chai"; + import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { time } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; + import { Reverter } from "@/test/helpers/reverter"; import { BlockGuardMock } from "@ethers-v6"; @@ -51,7 +53,9 @@ describe("BlockGuard", () => { it("should disallow to call in the same block", async () => { await expect( mock.multicall([mock.interface.encodeFunctionData("deposit"), mock.interface.encodeFunctionData("withdraw")]), - ).to.be.revertedWith("BlockGuard: locked"); + ) + .to.be.revertedWithCustomError(mock, "BlockGuardLocked") + .withArgs(await mock.DEPOSIT_WITHDRAW_RESOURCE(), FIRST); }); }); @@ -71,7 +75,9 @@ describe("BlockGuard", () => { it("should disallow to call in the same block", async () => { await expect( mock.multicall([mock.interface.encodeFunctionData("lock"), mock.interface.encodeFunctionData("lock")]), - ).to.be.revertedWith("BlockGuard: locked"); + ) + .to.be.revertedWithCustomError(mock, "BlockGuardLocked") + .withArgs(await mock.LOCK_LOCK_RESOURCE(), FIRST); }); }); });