diff --git a/.solcover.js b/.solcover.js index 28dd55c..21e655f 100644 --- a/.solcover.js +++ b/.solcover.js @@ -4,22 +4,22 @@ module.exports = { yul: true, yulDetails: { optimizerSteps: - 'dhfoDgvlfnTUtnIf' + // None of these can make stack problems worse + 'dhfoDgvlfnTUtnIf' + // None of these can make stack problems worse '[' + - 'xa[r]EscLM' + // Turn into SSA and simplify - 'cCTUtTOntnfDIl' + // Perform structural simplification - 'Lcl' + // Simplify again - 'Vcl [j]' + // Reverse SSA + 'xa[r]EscLM' + // Turn into SSA and simplify + 'cCTUtTOntnfDIl' + // Perform structural simplification + 'Lcl' + // Simplify again + 'Vcl [j]' + // Reverse SSA // should have good 'compilability' property here. - 'Tpel' + // Run functional expression inliner - 'xa[rl]' + // Prune a bit more in SSA - 'xa[r]cL' + // Turn into SSA again and simplify - // 'gvif' + // Run full inliner - 'CTUca[r]LSsTFOtfDnca[r]Ilc' + // SSA plus simplify + 'Tpel' + // Run functional expression inliner + 'xa[rl]' + // Prune a bit more in SSA + 'xa[r]cL' + // Turn into SSA again and simplify + 'gvf' + // Run full inliner + 'CTUca[r]LSsTFOtfDnca[r]Ilc' + // SSA plus simplify ']' + - 'jml[jl] VcTOcl jml', // Make source short and pretty + 'jml[jl] VcTOcl jml : fDnTOcm', // Make source short and pretty }, }, skipFiles: [ diff --git a/contracts/libraries/SafeERC20.sol b/contracts/libraries/SafeERC20.sol index 192e545..55a7192 100644 --- a/contracts/libraries/SafeERC20.sol +++ b/contracts/libraries/SafeERC20.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; import "../interfaces/IDaiLikePermit.sol"; import "../interfaces/IPermit2.sol"; import "../interfaces/IWETH.sol"; @@ -59,7 +59,7 @@ library SafeERC20 { /** * @notice Attempts to safely transfer tokens from one address to another. - * @dev If permit2 is true, uses the Permit2 standard; otherwise uses the standard ERC20 transferFrom. + * @dev If permit2 is true, uses the Permit2 standard; otherwise uses the standard ERC20 transferFrom. * Either requires `true` in return data, or requires target to be smart-contract and empty return data. * @param token The IERC20 token contract from which the tokens will be transferred. * @param from The address from which the tokens will be transferred. @@ -238,7 +238,7 @@ library SafeERC20 { } /** - * @notice Attempts to execute the `permit` function on the provided token with custom owner and spender parameters. + * @notice Attempts to execute the `permit` function on the provided token with custom owner and spender parameters. * Permit type is determined automatically based on permit calldata (IERC20Permit, IDaiLikePermit, and IPermit2). * @dev Wraps `tryPermit` function and forwards revert reason if permit fails. * @param token The IERC20 token to execute the permit function on. @@ -265,9 +265,9 @@ library SafeERC20 { * @notice The function attempts to call the permit function on a given ERC20 token. * @dev The function is designed to support a variety of permit functions, namely: IERC20Permit, IDaiLikePermit, and IPermit2. * It accommodates both Compact and Full formats of these permit types. - * Please note, it is expected that the `expiration` parameter for the compact Permit2 and the `deadline` parameter + * Please note, it is expected that the `expiration` parameter for the compact Permit2 and the `deadline` parameter * for the compact Permit are to be incremented by one before invoking this function. This approach is motivated by - * gas efficiency considerations; as the unlimited expiration period is likely to be the most common scenario, and + * gas efficiency considerations; as the unlimited expiration period is likely to be the most common scenario, and * zeros are cheaper to pass in terms of gas cost. Thus, callers should increment the expiration or deadline by one * before invocation for optimized performance. * @param token The address of the ERC20 token on which to call the permit function. @@ -349,7 +349,7 @@ library SafeERC20 { mstore(add(ptr, 0x24), token) // store token calldatacopy(add(ptr, 0x50), permit.offset, 0x14) // store amount = copy permit.offset 0x00..0x13 - // and(0xffffffffffff, ...) - conversion to uint48 + // and(0xffffffffffff, ...) - conversion to uint48 mstore(add(ptr, 0x64), and(0xffffffffffff, sub(shr(224, calldataload(add(permit.offset, 0x14))), 1))) // store expiration = ((permit.offset 0x14..0x17 - 1) & 0xffffffffffff) mstore(add(ptr, 0x84), shr(224, calldataload(add(permit.offset, 0x18)))) // store nonce = copy permit.offset 0x18..0x1b mstore(add(ptr, 0xa4), spender) // store spender @@ -383,7 +383,7 @@ library SafeERC20 { * @param selector The function signature that is to be called on the token contract. * @param to The address to which the token amount will be transferred. * @param amount The token amount to be transferred. - * @return success A boolean indicating if the call was successful. Returns 'true' on success and 'false' on failure. + * @return success A boolean indicating if the call was successful. Returns 'true' on success and 'false' on failure. * In case of success but no returned data, validates that the contract code exists. * In case of returned data, ensures that it's a boolean `true`. */ diff --git a/contracts/mocks/ERC20PermitMock.sol b/contracts/mocks/ERC20PermitMock.sol index 7984a8b..38dd410 100644 --- a/contracts/mocks/ERC20PermitMock.sol +++ b/contracts/mocks/ERC20PermitMock.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; -import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; import "./TokenMock.sol"; contract ERC20PermitMock is ERC20Permit, TokenMock { diff --git a/contracts/mocks/TokenCustomDecimalsMock.sol b/contracts/mocks/TokenCustomDecimalsMock.sol index 8785280..6261ff5 100644 --- a/contracts/mocks/TokenCustomDecimalsMock.sol +++ b/contracts/mocks/TokenCustomDecimalsMock.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; contract TokenCustomDecimalsMock is ERC20Permit, Ownable { uint8 internal immutable _decimals; @@ -13,7 +13,7 @@ contract TokenCustomDecimalsMock is ERC20Permit, Ownable { string memory symbol, uint256 amount, uint8 decimals_ - ) ERC20(name, symbol) ERC20Permit(name) { + ) ERC20(name, symbol) ERC20Permit(name) Ownable(msg.sender) { _mint(msg.sender, amount); _decimals = decimals_; } diff --git a/contracts/mocks/TokenMock.sol b/contracts/mocks/TokenMock.sol index 78cc3a8..bf257ab 100644 --- a/contracts/mocks/TokenMock.sol +++ b/contracts/mocks/TokenMock.sol @@ -7,7 +7,7 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract TokenMock is ERC20, Ownable { // solhint-disable-next-line no-empty-blocks - constructor(string memory name, string memory symbol) ERC20(name, symbol) {} + constructor(string memory name, string memory symbol) ERC20(name, symbol) Ownable(msg.sender) {} function mint(address account, uint256 amount) external onlyOwner { _mint(account, amount); diff --git a/contracts/tests/mocks/DaiLikePermitMock.sol b/contracts/tests/mocks/DaiLikePermitMock.sol index a061446..d03c6a8 100644 --- a/contracts/tests/mocks/DaiLikePermitMock.sol +++ b/contracts/tests/mocks/DaiLikePermitMock.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; -import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; contract DaiLikePermitMock is ERC20Permit { // bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)"); diff --git a/contracts/tests/mocks/ERC1271WalletMock.sol b/contracts/tests/mocks/ERC1271WalletMock.sol index 480eb3d..6ef3857 100644 --- a/contracts/tests/mocks/ERC1271WalletMock.sol +++ b/contracts/tests/mocks/ERC1271WalletMock.sol @@ -7,9 +7,7 @@ import "@openzeppelin/contracts/interfaces/IERC1271.sol"; import "../../libraries/ECDSA.sol"; contract ERC1271WalletMock is Ownable, IERC1271 { - constructor(address originalOwner) { - transferOwnership(originalOwner); - } + constructor(address originalOwner) Ownable(originalOwner) {} // solhint-disable-line no-empty-blocks function isValidSignature(bytes32 hash, bytes calldata signature) public view override returns (bytes4 magicValue) { return ECDSA.recover(hash, signature) == owner() ? this.isValidSignature.selector : bytes4(0); diff --git a/contracts/tests/mocks/SafeERC20Helper.sol b/contracts/tests/mocks/SafeERC20Helper.sol index 5af12a5..12595ef 100644 --- a/contracts/tests/mocks/SafeERC20Helper.sol +++ b/contracts/tests/mocks/SafeERC20Helper.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; import "../../libraries/SafeERC20.sol"; contract ERC20ReturnFalseMock { diff --git a/hardhat.config.ts b/hardhat.config.ts index 139de73..9e44228 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -8,6 +8,7 @@ import '@nomicfoundation/hardhat-verify'; require('solidity-coverage'); // require because no TS typings available import dotenv from 'dotenv'; import { HardhatUserConfig } from 'hardhat/config'; +import { HardhatNetworkUserConfig } from 'hardhat/types'; import networks from './hardhat.networks'; dotenv.config(); @@ -18,14 +19,20 @@ declare module 'hardhat/types/runtime' { } } +function getNetwork(): string { + const index = process.argv.findIndex((arg) => arg === '--network') + 1; + return index !== 0 ? process.argv[index] : 'unknown'; +} + const config: HardhatUserConfig = { solidity: { - version: '0.8.15', + version: '0.8.23', settings: { optimizer: { enabled: true, runs: 1000000, }, + evmVersion: (networks[getNetwork()] as HardhatNetworkUserConfig)?.hardfork || 'shanghai', viaIR: true, }, }, diff --git a/hardhat.networks.ts b/hardhat.networks.ts index 07982b6..7667b35 100644 --- a/hardhat.networks.ts +++ b/hardhat.networks.ts @@ -9,28 +9,41 @@ const networks: NetworksUserConfig = { }, }; -function register(name: string, chainId: number, url?: string, privateKey?: string) { - if (url && privateKey) { +const etherscan: { apiKey: {[key: string]: string}, customChains: [object] } = { apiKey: {}, customChains: [{}] }; + +function register(name: string, chainId: number, url?: string, privateKey?: string, etherscanNetworkName?: string, etherscanKey?: string, hardfork: string = 'paris') { + if (url && privateKey && etherscanNetworkName && etherscanKey) { networks[name] = { url, chainId, accounts: [privateKey], + hardfork, }; + etherscan.apiKey[etherscanNetworkName] = etherscanKey; console.log(`Network '${name}' registered`); } else { console.log(`Network '${name}' not registered`); } } -register('mainnet', 1, process.env.MAINNET_RPC_URL, process.env.MAINNET_PRIVATE_KEY); -register('bsc', 56, process.env.BSC_RPC_URL, process.env.BSC_PRIVATE_KEY); -register('kovan', 42, process.env.KOVAN_RPC_URL, process.env.KOVAN_PRIVATE_KEY); -register('optimistic', 10, process.env.OPTIMISTIC_RPC_URL, process.env.OPTIMISTIC_PRIVATE_KEY); -register('kovan-optimistic', 69, process.env.KOVAN_OPTIMISTIC_RPC_URL, process.env.KOVAN_OPTIMISTIC_PRIVATE_KEY); -register('matic', 137, process.env.MATIC_RPC_URL, process.env.MATIC_PRIVATE_KEY); -register('arbitrum', 42161, process.env.ARBITRUM_RPC_URL, process.env.ARBITRUM_PRIVATE_KEY); -register('ropsten', 3, process.env.ROPSTEN_RPC_URL, process.env.ROPSTEN_PRIVATE_KEY); -register('xdai', 100, process.env.XDAI_RPC_URL, process.env.XDAI_PRIVATE_KEY); -register('avax', 43114, process.env.AVAX_RPC_URL, process.env.AVAX_PRIVATE_KEY); +function registerCustom(name: string, chainId: number, url?: string, privateKey?: string, etherscanKey?: string, apiURL?: string, browserURL?: string, hardfork = 'paris') { + if (url && privateKey && etherscanKey) { + register(name, chainId, hardfork, url, privateKey, name, etherscanKey); + etherscan.customChains.push({ network: name, chainId, urls: { apiURL, browserURL } }); + } +} + +register('mainnet', 1, process.env.MAINNET_RPC_URL, process.env.MAINNET_PRIVATE_KEY, 'mainnet', process.env.MAINNET_ETHERSCAN_KEY, 'shanghai'); +register('bsc', 56, process.env.BSC_RPC_URL, process.env.BSC_PRIVATE_KEY, 'bsc', process.env.BSC_ETHERSCAN_KEY); +register('kovan', 42, process.env.KOVAN_RPC_URL, process.env.KOVAN_PRIVATE_KEY, 'kovan', process.env.KOVAN_ETHERSCAN_KEY); +register('optimistic', 10, process.env.OPTIMISTIC_RPC_URL, process.env.OPTIMISTIC_PRIVATE_KEY, 'optimisticEthereum', process.env.OPTIMISTIC_ETHERSCAN_KEY); +register('matic', 137, process.env.MATIC_RPC_URL, process.env.MATIC_PRIVATE_KEY, 'polygon', process.env.MATIC_ETHERSCAN_KEY); +register('arbitrum', 42161, process.env.ARBITRUM_RPC_URL, process.env.ARBITRUM_PRIVATE_KEY, 'arbitrumOne', process.env.ARBITRUM_ETHERSCAN_KEY); +register('xdai', 100, process.env.XDAI_RPC_URL, process.env.XDAI_PRIVATE_KEY, 'gnosis', process.env.XDAI_ETHERSCAN_KEY); +register('avax', 43114, process.env.AVAX_RPC_URL, process.env.AVAX_PRIVATE_KEY, 'avalanche', process.env.AVAX_ETHERSCAN_KEY); +register('fantom', 250, process.env.FANTOM_RPC_URL, process.env.FANTOM_PRIVATE_KEY, 'opera', process.env.FANTOM_ETHERSCAN_KEY); +register('aurora', 1313161554, process.env.AURORA_RPC_URL, process.env.AURORA_PRIVATE_KEY, 'aurora', process.env.AURORA_ETHERSCAN_KEY); +register('base', 8453, process.env.BASE_RPC_URL, process.env.BASE_PRIVATE_KEY, 'base', process.env.BASE_ETHERSCAN_KEY); +registerCustom('klaytn', 8217, process.env.KLAYTN_RPC_URL, process.env.KLAYTN_PRIVATE_KEY, process.env.KLAYTN_ETHERSCAN_KEY, 'https://scope.klaytn.com/', 'https://scope.klaytn.com/'); export default networks; diff --git a/package.json b/package.json index 7cec9d6..f832ca3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@1inch/solidity-utils", - "version": "3.2.0", + "version": "3.3.0", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", "repository": { @@ -31,7 +31,7 @@ "@metamask/eth-sig-util": "6.0.0", "@nomicfoundation/hardhat-ethers": "3.0.3", "@nomicfoundation/hardhat-network-helpers": "1.0.8", - "@openzeppelin/contracts": "4.9.2", + "@openzeppelin/contracts": "5.0.0", "@uniswap/permit2-sdk": "1.2.0", "ethereumjs-util": "7.1.5", "ethers": "6.6.3", diff --git a/test/Permitable.test.ts b/test/Permitable.test.ts index 3c1c24a..74e2fcd 100644 --- a/test/Permitable.test.ts +++ b/test/Permitable.test.ts @@ -68,7 +68,7 @@ describe('Permitable', function () { const deadline = block ? block.timestamp - 1000 : 1000; const permit = await getPermit(signer1, erc20PermitMock, '1', chainId, await permitableMock.getAddress(), value.toString(), deadline.toString()); - await expect(permitableMock.mockPermit(erc20PermitMock, permit)).to.be.revertedWith('ERC20Permit: expired deadline'); + await expect(permitableMock.mockPermit(erc20PermitMock, permit)).to.be.revertedWithCustomError(erc20PermitMock, 'ERC2612ExpiredSignature'); }); it('should not be permitted for IERC20Permit', async function () { @@ -100,9 +100,7 @@ describe('Permitable', function () { s, ]), ); - await expect(permitableMock.mockPermit(erc20PermitMock, permit)).to.be.revertedWith( - 'ERC20Permit: invalid signature', - ); + await expect(permitableMock.mockPermit(erc20PermitMock, permit)).to.be.revertedWithCustomError(erc20PermitMock, 'ERC2612InvalidSigner'); }); it('should be permitted for IDaiLikePermit', async function () { diff --git a/test/contracts/SafestERC20.test.ts b/test/contracts/SafestERC20.test.ts index a3b9413..b4d0011 100644 --- a/test/contracts/SafestERC20.test.ts +++ b/test/contracts/SafestERC20.test.ts @@ -301,7 +301,7 @@ describe('SafeERC20', function () { expect(received).to.be.equal(ether('1')); if (hre.__SOLIDITY_COVERAGE_RUNNING === undefined) { expect(await countInstructions(ethers.provider, tx.logs[0].transactionHash, ['STATICCALL', 'CALL', 'MSTORE', 'MLOAD', 'SSTORE', 'SLOAD'])).to.be.deep.equal([ - 0, 1, 6, 3, 1, 2, + 0, 1, 6, 1, 1, 2, ]); } }); @@ -313,7 +313,7 @@ describe('SafeERC20', function () { ); if (hre.__SOLIDITY_COVERAGE_RUNNING === undefined) { expect(await countInstructions(ethers.provider, tx.hash, ['STATICCALL', 'CALL', 'MSTORE', 'MLOAD', 'SSTORE', 'SLOAD'])).to.be.deep.equal([ - 0, 0, 1, 1, 0, 1, + 0, 0, 1, 0, 0, 1, ]); } }); diff --git a/test/contracts/StringUtil.test.ts b/test/contracts/StringUtil.test.ts index 39febf1..171955f 100644 --- a/test/contracts/StringUtil.test.ts +++ b/test/contracts/StringUtil.test.ts @@ -69,31 +69,31 @@ describe('StringUtil', function () { it('Uint 256', () => testGasUint256(uint256TestValue, 834)); - it('Uint 256 naive', () => testGasNaiveUint256(uint256TestValue, 16277)); + it('Uint 256 naive', () => testGasNaiveUint256(uint256TestValue, 16369)); it('Uint 256 as bytes', () => testGasBytes(uint256TestValue, 716)); - it('Uint 256 as bytes naive', () => testGasNaiveBytes(uint256TestValue, 16159)); + it('Uint 256 as bytes naive', () => testGasNaiveBytes(uint256TestValue, 16251)); it('Uint 128', () => testGasUint256(uint128TestValue, 834)); - it('Uint 128 naive', () => testGasNaiveUint256(uint128TestValue, 16277)); + it('Uint 128 naive', () => testGasNaiveUint256(uint128TestValue, 16369)); it('Very long byte array gas', () => testGasBytes(veryLongArray, 1766)); - it('Very long byte array gas naive', () => testGasNaiveBytes(veryLongArray, 33286)); + it('Very long byte array gas naive', () => testGasNaiveBytes(veryLongArray, 33483)); it('Extremely long byte array gas', () => testGasBytes(extremelyLongArray, 17009)); - it('Extremely long byte array gas naive', () => testGasNaiveBytes(extremelyLongArray, 489888)); + it('Extremely long byte array gas naive', () => testGasNaiveBytes(extremelyLongArray, 492884)); it('Empty bytes', () => testGasBytes(emptyBytes, 191)); - it('Empty bytes naive', () => testGasNaiveBytes(emptyBytes, 499)); + it('Empty bytes naive', () => testGasNaiveBytes(emptyBytes, 495)); it('Single byte', () => testGasBytes(singleByte, 716)); - it('Single byte naive', () => testGasNaiveBytes(singleByte, 988)); + it('Single byte naive', () => testGasNaiveBytes(singleByte, 987)); async function testGasUint256(value: string, expectedGas: number) { const { stringUtilTest } = await loadFixture(deployStringUtilTest); diff --git a/yarn.lock b/yarn.lock index a53879a..a7e6ef6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -824,10 +824,10 @@ "@nomicfoundation/solidity-analyzer-win32-ia32-msvc" "0.1.1" "@nomicfoundation/solidity-analyzer-win32-x64-msvc" "0.1.1" -"@openzeppelin/contracts@4.9.2": - version "4.9.2" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.2.tgz#1cb2d5e4d3360141a17dbc45094a8cad6aac16c1" - integrity sha512-mO+y6JaqXjWeMh9glYVzVu8HYPGknAAnWyxTRhGeckOruyXQMNnlcW6w/Dx9ftLeIQk6N+ZJFuVmTwF7lEIFrg== +"@openzeppelin/contracts@5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.0.0.tgz#ee0e4b4564f101a5c4ee398cd4d73c0bd92b289c" + integrity sha512-bv2sdS6LKqVVMLI5+zqnNrNU/CA+6z6CmwFXm/MzmOPBRSO5reEJN7z0Gbzvs0/bv/MZZXNklubpwy3v2+azsw== "@pkgjs/parseargs@^0.11.0": version "0.11.0"