Browse Source

Remove the draft prefix to ERC20Permit (#3793)

Co-authored-by: Francisco <frangio.1@gmail.com>
Hadrien Croubois 2 years ago
parent
commit
0b6becd49f

+ 4 - 0
CHANGELOG.md

@@ -6,6 +6,10 @@
  * `ERC20Votes`: optimize by using unchecked arithmetic. ([#3748](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3748))
  * `ERC20Votes`: optimize by using unchecked arithmetic. ([#3748](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3748))
  * `Initializable`: optimize `_disableInitializers` by using `!=` instead of `<`. ([#3787](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3787))
  * `Initializable`: optimize `_disableInitializers` by using `!=` instead of `<`. ([#3787](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3787))
 
 
+### Deprecations
+
+ * `ERC20Permit`: Added the file `IERC20Permit.sol` and `ERC20Permit.sol` and deprecated `draft-IERC20Permit.sol` and `draft-ERC20Permit.sol` since [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612) is no longer a Draft. Developers are encouraged to update their imports. ([#3793](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3793))
+
 ## Unreleased
 ## Unreleased
 
 
  * `TimelockController`: Added a new `admin` constructor parameter that is assigned the admin role instead of the deployer account. ([#3722](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3722))
  * `TimelockController`: Added a new `admin` constructor parameter that is assigned the admin role instead of the deployer account. ([#3722](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3722))

+ 8 - 0
contracts/interfaces/IERC2612.sol

@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: MIT
+// OpenZeppelin Contracts v4.4.1 (interfaces/IERC2612.sol)
+
+pragma solidity ^0.8.0;
+
+import "../token/ERC20/extensions/IERC20Permit.sol";
+
+interface IERC2612 is IERC20Permit {}

+ 2 - 3
contracts/interfaces/draft-IERC2612.sol

@@ -1,8 +1,7 @@
 // SPDX-License-Identifier: MIT
 // SPDX-License-Identifier: MIT
-// OpenZeppelin Contracts v4.4.1 (interfaces/draft-IERC2612.sol)
 
 
 pragma solidity ^0.8.0;
 pragma solidity ^0.8.0;
 
 
-import "../token/ERC20/extensions/draft-IERC20Permit.sol";
+// EIP-2612 is Final as of 2022-11-01. This file is deprecated.
 
 
-interface IERC2612 is IERC20Permit {}
+import "./IERC2612.sol";

+ 1 - 1
contracts/mocks/ERC20PermitMock.sol

@@ -2,7 +2,7 @@
 
 
 pragma solidity ^0.8.0;
 pragma solidity ^0.8.0;
 
 
-import "../token/ERC20/extensions/draft-ERC20Permit.sol";
+import "../token/ERC20/extensions/ERC20Permit.sol";
 
 
 contract ERC20PermitMock is ERC20Permit {
 contract ERC20PermitMock is ERC20Permit {
     constructor(
     constructor(

+ 1 - 1
contracts/mocks/SafeERC20Helper.sol

@@ -4,7 +4,7 @@ pragma solidity ^0.8.0;
 
 
 import "../utils/Context.sol";
 import "../utils/Context.sol";
 import "../token/ERC20/IERC20.sol";
 import "../token/ERC20/IERC20.sol";
-import "../token/ERC20/extensions/draft-ERC20Permit.sol";
+import "../token/ERC20/extensions/ERC20Permit.sol";
 import "../token/ERC20/utils/SafeERC20.sol";
 import "../token/ERC20/utils/SafeERC20.sol";
 
 
 contract ERC20ReturnFalseMock is Context {
 contract ERC20ReturnFalseMock is Context {

+ 2 - 10
contracts/token/ERC20/README.adoc

@@ -31,10 +31,6 @@ Finally, there are some utilities to interact with ERC20 contracts in various wa
 * {SafeERC20}: a wrapper around the interface that eliminates the need to handle boolean return values.
 * {SafeERC20}: a wrapper around the interface that eliminates the need to handle boolean return values.
 * {TokenTimelock}: hold tokens for a beneficiary until a specified time.
 * {TokenTimelock}: hold tokens for a beneficiary until a specified time.
 
 
-The following related EIPs are in draft status.
-
-- {ERC20Permit}
-
 NOTE: This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC20 (such as <<ERC20-_mint-address-uint256-,`_mint`>>) and expose them as external functions in the way they prefer. On the other hand, xref:ROOT:erc20.adoc#Presets[ERC20 Presets] (such as {ERC20PresetMinterPauser}) are designed using opinionated patterns to provide developers with ready to use, deployable contracts.
 NOTE: This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC20 (such as <<ERC20-_mint-address-uint256-,`_mint`>>) and expose them as external functions in the way they prefer. On the other hand, xref:ROOT:erc20.adoc#Presets[ERC20 Presets] (such as {ERC20PresetMinterPauser}) are designed using opinionated patterns to provide developers with ready to use, deployable contracts.
 
 
 == Core
 == Core
@@ -53,6 +49,8 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel
 
 
 {{ERC20Pausable}}
 {{ERC20Pausable}}
 
 
+{{ERC20Permit}}
+
 {{ERC20Snapshot}}
 {{ERC20Snapshot}}
 
 
 {{ERC20Votes}}
 {{ERC20Votes}}
@@ -65,12 +63,6 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel
 
 
 {{ERC4626}}
 {{ERC4626}}
 
 
-== Draft EIPs
-
-The following EIPs are still in Draft status. Due to their nature as drafts, the details of these contracts may change and we cannot guarantee their xref:ROOT:releases-stability.adoc[stability]. Minor releases of OpenZeppelin Contracts may contain breaking changes for the contracts in this directory, which will be duly announced in the https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CHANGELOG.md[changelog]. The EIPs included here are used by projects in production and this may make them less likely to change significantly.
-
-{{ERC20Permit}}
-
 == Presets
 == Presets
 
 
 These contracts are preconfigured combinations of the above features. They can be used through inheritance or as models to copy and paste their source code.
 These contracts are preconfigured combinations of the above features. They can be used through inheritance or as models to copy and paste their source code.

+ 95 - 0
contracts/token/ERC20/extensions/ERC20Permit.sol

@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: MIT
+// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/extensions/ERC20Permit.sol)
+
+pragma solidity ^0.8.0;
+
+import "./IERC20Permit.sol";
+import "../ERC20.sol";
+import "../../../utils/cryptography/ECDSA.sol";
+import "../../../utils/cryptography/EIP712.sol";
+import "../../../utils/Counters.sol";
+
+/**
+ * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
+ * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
+ *
+ * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
+ * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
+ * need to send a transaction, and thus is not required to hold Ether at all.
+ *
+ * _Available since v3.4._
+ */
+abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 {
+    using Counters for Counters.Counter;
+
+    mapping(address => Counters.Counter) private _nonces;
+
+    // solhint-disable-next-line var-name-mixedcase
+    bytes32 private constant _PERMIT_TYPEHASH =
+        keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
+    /**
+     * @dev In previous versions `_PERMIT_TYPEHASH` was declared as `immutable`.
+     * However, to ensure consistency with the upgradeable transpiler, we will continue
+     * to reserve a slot.
+     * @custom:oz-renamed-from _PERMIT_TYPEHASH
+     */
+    // solhint-disable-next-line var-name-mixedcase
+    bytes32 private _PERMIT_TYPEHASH_DEPRECATED_SLOT;
+
+    /**
+     * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
+     *
+     * It's a good idea to use the same `name` that is defined as the ERC20 token name.
+     */
+    constructor(string memory name) EIP712(name, "1") {}
+
+    /**
+     * @dev See {IERC20Permit-permit}.
+     */
+    function permit(
+        address owner,
+        address spender,
+        uint256 value,
+        uint256 deadline,
+        uint8 v,
+        bytes32 r,
+        bytes32 s
+    ) public virtual override {
+        require(block.timestamp <= deadline, "ERC20Permit: expired deadline");
+
+        bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));
+
+        bytes32 hash = _hashTypedDataV4(structHash);
+
+        address signer = ECDSA.recover(hash, v, r, s);
+        require(signer == owner, "ERC20Permit: invalid signature");
+
+        _approve(owner, spender, value);
+    }
+
+    /**
+     * @dev See {IERC20Permit-nonces}.
+     */
+    function nonces(address owner) public view virtual override returns (uint256) {
+        return _nonces[owner].current();
+    }
+
+    /**
+     * @dev See {IERC20Permit-DOMAIN_SEPARATOR}.
+     */
+    // solhint-disable-next-line func-name-mixedcase
+    function DOMAIN_SEPARATOR() external view override returns (bytes32) {
+        return _domainSeparatorV4();
+    }
+
+    /**
+     * @dev "Consume a nonce": return the current value and increment.
+     *
+     * _Available since v4.1._
+     */
+    function _useNonce(address owner) internal virtual returns (uint256 current) {
+        Counters.Counter storage nonce = _nonces[owner];
+        current = nonce.current();
+        nonce.increment();
+    }
+}

+ 1 - 1
contracts/token/ERC20/extensions/ERC20Votes.sol

@@ -3,7 +3,7 @@
 
 
 pragma solidity ^0.8.0;
 pragma solidity ^0.8.0;
 
 
-import "./draft-ERC20Permit.sol";
+import "./ERC20Permit.sol";
 import "../../../utils/math/Math.sol";
 import "../../../utils/math/Math.sol";
 import "../../../governance/utils/IVotes.sol";
 import "../../../governance/utils/IVotes.sol";
 import "../../../utils/math/SafeCast.sol";
 import "../../../utils/math/SafeCast.sol";

+ 60 - 0
contracts/token/ERC20/extensions/IERC20Permit.sol

@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: MIT
+// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Permit.sol)
+
+pragma solidity ^0.8.0;
+
+/**
+ * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
+ * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
+ *
+ * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
+ * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
+ * need to send a transaction, and thus is not required to hold Ether at all.
+ */
+interface IERC20Permit {
+    /**
+     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
+     * given ``owner``'s signed approval.
+     *
+     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
+     * ordering also apply here.
+     *
+     * Emits an {Approval} event.
+     *
+     * Requirements:
+     *
+     * - `spender` cannot be the zero address.
+     * - `deadline` must be a timestamp in the future.
+     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
+     * over the EIP712-formatted function arguments.
+     * - the signature must use ``owner``'s current nonce (see {nonces}).
+     *
+     * For more information on the signature format, see the
+     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
+     * section].
+     */
+    function permit(
+        address owner,
+        address spender,
+        uint256 value,
+        uint256 deadline,
+        uint8 v,
+        bytes32 r,
+        bytes32 s
+    ) external;
+
+    /**
+     * @dev Returns the current nonce for `owner`. This value must be
+     * included whenever a signature is generated for {permit}.
+     *
+     * Every successful call to {permit} increases ``owner``'s nonce by one. This
+     * prevents a signature from being used multiple times.
+     */
+    function nonces(address owner) external view returns (uint256);
+
+    /**
+     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
+     */
+    // solhint-disable-next-line func-name-mixedcase
+    function DOMAIN_SEPARATOR() external view returns (bytes32);
+}

+ 2 - 90
contracts/token/ERC20/extensions/draft-ERC20Permit.sol

@@ -1,95 +1,7 @@
 // SPDX-License-Identifier: MIT
 // SPDX-License-Identifier: MIT
-// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/extensions/draft-ERC20Permit.sol)
 
 
 pragma solidity ^0.8.0;
 pragma solidity ^0.8.0;
 
 
-import "./draft-IERC20Permit.sol";
-import "../ERC20.sol";
-import "../../../utils/cryptography/ECDSA.sol";
-import "../../../utils/cryptography/EIP712.sol";
-import "../../../utils/Counters.sol";
+// EIP-2612 is Final as of 2022-11-01. This file is deprecated.
 
 
-/**
- * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
- * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
- *
- * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
- * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
- * need to send a transaction, and thus is not required to hold Ether at all.
- *
- * _Available since v3.4._
- */
-abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 {
-    using Counters for Counters.Counter;
-
-    mapping(address => Counters.Counter) private _nonces;
-
-    // solhint-disable-next-line var-name-mixedcase
-    bytes32 private constant _PERMIT_TYPEHASH =
-        keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
-    /**
-     * @dev In previous versions `_PERMIT_TYPEHASH` was declared as `immutable`.
-     * However, to ensure consistency with the upgradeable transpiler, we will continue
-     * to reserve a slot.
-     * @custom:oz-renamed-from _PERMIT_TYPEHASH
-     */
-    // solhint-disable-next-line var-name-mixedcase
-    bytes32 private _PERMIT_TYPEHASH_DEPRECATED_SLOT;
-
-    /**
-     * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
-     *
-     * It's a good idea to use the same `name` that is defined as the ERC20 token name.
-     */
-    constructor(string memory name) EIP712(name, "1") {}
-
-    /**
-     * @dev See {IERC20Permit-permit}.
-     */
-    function permit(
-        address owner,
-        address spender,
-        uint256 value,
-        uint256 deadline,
-        uint8 v,
-        bytes32 r,
-        bytes32 s
-    ) public virtual override {
-        require(block.timestamp <= deadline, "ERC20Permit: expired deadline");
-
-        bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));
-
-        bytes32 hash = _hashTypedDataV4(structHash);
-
-        address signer = ECDSA.recover(hash, v, r, s);
-        require(signer == owner, "ERC20Permit: invalid signature");
-
-        _approve(owner, spender, value);
-    }
-
-    /**
-     * @dev See {IERC20Permit-nonces}.
-     */
-    function nonces(address owner) public view virtual override returns (uint256) {
-        return _nonces[owner].current();
-    }
-
-    /**
-     * @dev See {IERC20Permit-DOMAIN_SEPARATOR}.
-     */
-    // solhint-disable-next-line func-name-mixedcase
-    function DOMAIN_SEPARATOR() external view override returns (bytes32) {
-        return _domainSeparatorV4();
-    }
-
-    /**
-     * @dev "Consume a nonce": return the current value and increment.
-     *
-     * _Available since v4.1._
-     */
-    function _useNonce(address owner) internal virtual returns (uint256 current) {
-        Counters.Counter storage nonce = _nonces[owner];
-        current = nonce.current();
-        nonce.increment();
-    }
-}
+import "./ERC20Permit.sol";

+ 2 - 55
contracts/token/ERC20/extensions/draft-IERC20Permit.sol

@@ -1,60 +1,7 @@
 // SPDX-License-Identifier: MIT
 // SPDX-License-Identifier: MIT
-// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)
 
 
 pragma solidity ^0.8.0;
 pragma solidity ^0.8.0;
 
 
-/**
- * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
- * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
- *
- * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
- * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
- * need to send a transaction, and thus is not required to hold Ether at all.
- */
-interface IERC20Permit {
-    /**
-     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
-     * given ``owner``'s signed approval.
-     *
-     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
-     * ordering also apply here.
-     *
-     * Emits an {Approval} event.
-     *
-     * Requirements:
-     *
-     * - `spender` cannot be the zero address.
-     * - `deadline` must be a timestamp in the future.
-     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
-     * over the EIP712-formatted function arguments.
-     * - the signature must use ``owner``'s current nonce (see {nonces}).
-     *
-     * For more information on the signature format, see the
-     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
-     * section].
-     */
-    function permit(
-        address owner,
-        address spender,
-        uint256 value,
-        uint256 deadline,
-        uint8 v,
-        bytes32 r,
-        bytes32 s
-    ) external;
+// EIP-2612 is Final as of 2022-11-01. This file is deprecated.
 
 
-    /**
-     * @dev Returns the current nonce for `owner`. This value must be
-     * included whenever a signature is generated for {permit}.
-     *
-     * Every successful call to {permit} increases ``owner``'s nonce by one. This
-     * prevents a signature from being used multiple times.
-     */
-    function nonces(address owner) external view returns (uint256);
-
-    /**
-     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
-     */
-    // solhint-disable-next-line func-name-mixedcase
-    function DOMAIN_SEPARATOR() external view returns (bytes32);
-}
+import "./IERC20Permit.sol";

+ 1 - 1
contracts/token/ERC20/utils/SafeERC20.sol

@@ -4,7 +4,7 @@
 pragma solidity ^0.8.0;
 pragma solidity ^0.8.0;
 
 
 import "../IERC20.sol";
 import "../IERC20.sol";
-import "../extensions/draft-IERC20Permit.sol";
+import "../extensions/IERC20Permit.sol";
 import "../../../utils/Address.sol";
 import "../../../utils/Address.sol";
 
 
 /**
 /**

+ 2 - 2
docs/modules/ROOT/pages/governance.adoc

@@ -45,7 +45,7 @@ The voting power of each account in our governance setup will be determined by a
 pragma solidity ^0.8.2;
 pragma solidity ^0.8.2;
 
 
 import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
 import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
-import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
+import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
 import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
 import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
 
 
 contract MyToken is ERC20, ERC20Permit, ERC20Votes {
 contract MyToken is ERC20, ERC20Permit, ERC20Votes {
@@ -83,7 +83,7 @@ If your project already has a live token that does not include ERC20Votes and is
 pragma solidity ^0.8.2;
 pragma solidity ^0.8.2;
 
 
 import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
 import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
-import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
+import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
 import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
 import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
 import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Wrapper.sol";
 import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Wrapper.sol";
 
 

+ 5 - 2
scripts/migrate-imports.js

@@ -10,8 +10,8 @@ const pathUpdates = {
   'cryptography/ECDSA.sol': 'utils/cryptography/ECDSA.sol',
   'cryptography/ECDSA.sol': 'utils/cryptography/ECDSA.sol',
   'cryptography/MerkleProof.sol': 'utils/cryptography/MerkleProof.sol',
   'cryptography/MerkleProof.sol': 'utils/cryptography/MerkleProof.sol',
   'drafts/EIP712.sol': 'utils/cryptography/EIP712.sol',
   'drafts/EIP712.sol': 'utils/cryptography/EIP712.sol',
-  'drafts/ERC20Permit.sol': 'token/ERC20/extensions/draft-ERC20Permit.sol',
-  'drafts/IERC20Permit.sol': 'token/ERC20/extensions/draft-IERC20Permit.sol',
+  'drafts/ERC20Permit.sol': 'token/ERC20/extensions/ERC20Permit.sol',
+  'drafts/IERC20Permit.sol': 'token/ERC20/extensions/IERC20Permit.sol',
   'GSN/Context.sol': 'utils/Context.sol',
   'GSN/Context.sol': 'utils/Context.sol',
   // 'GSN/GSNRecipientERC20Fee.sol': undefined,
   // 'GSN/GSNRecipientERC20Fee.sol': undefined,
   // 'GSN/GSNRecipientSignature.sol': undefined,
   // 'GSN/GSNRecipientSignature.sol': undefined,
@@ -87,6 +87,9 @@ const pathUpdates = {
   'utils/ReentrancyGuard.sol': 'security/ReentrancyGuard.sol',
   'utils/ReentrancyGuard.sol': 'security/ReentrancyGuard.sol',
   'utils/SafeCast.sol': 'utils/math/SafeCast.sol',
   'utils/SafeCast.sol': 'utils/math/SafeCast.sol',
   // 'utils/Strings.sol': undefined,
   // 'utils/Strings.sol': undefined,
+  'utils/cryptography/draft-EIP712.sol': 'utils/cryptography/EIP712.sol',
+  'token/ERC20/extensions/draft-ERC20Permit.sol': 'token/ERC20/extensions/ERC20Permit.sol',
+  'token/ERC20/extensions/draft-IERC20Permit.sol': 'token/ERC20/extensions/IERC20Permit.sol',
 };
 };
 
 
 async function main (paths = [ 'contracts' ]) {
 async function main (paths = [ 'contracts' ]) {