diff --git a/e2e/fixture-projects/compatability-check/preprocess.sh b/e2e/fixture-projects/compatability-check/preprocess.sh index 29d2272ff..3a917d264 100755 --- a/e2e/fixture-projects/compatability-check/preprocess.sh +++ b/e2e/fixture-projects/compatability-check/preprocess.sh @@ -38,11 +38,11 @@ cat < "$SCRIPT_DIR/package.json" "typescript": "^5.1.6" }, "dependencies": { - "@matterlabs/hardhat-zksync-deploy": "0.8.0", - "@matterlabs/hardhat-zksync-solc": "1.1.4", - "@matterlabs/hardhat-zksync-node": "0.1.0", - "@matterlabs/hardhat-zksync-upgradable": "0.3.1", - "hardhat": "^2.19.4", + "@matterlabs/hardhat-zksync-deploy": "^0.9.0", + "@matterlabs/hardhat-zksync-solc": "^1.1.4", + "@matterlabs/hardhat-zksync-node": "^0.1.0", + "@matterlabs/hardhat-zksync-upgradable": "^0.4.0", + "hardhat": "^2.14.0", "ethers": "^5.7.2", "zksync-ethers": "^5.0.0", "@matterlabs/zksync-contracts": "^0.6.1", diff --git a/e2e/fixture-projects/hardhat-zksync/contracts/Bar.sol b/e2e/fixture-projects/hardhat-zksync/contracts/Bar.sol new file mode 100644 index 000000000..3ca517497 --- /dev/null +++ b/e2e/fixture-projects/hardhat-zksync/contracts/Bar.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; +pragma abicoder v2; + +import "./Foo.sol"; + +contract Bar { + Foo public foo = new Foo(); +} diff --git a/e2e/fixture-projects/hardhat-zksync/contracts/Box.sol b/e2e/fixture-projects/hardhat-zksync/contracts/Box.sol new file mode 100644 index 000000000..2c95bac7a --- /dev/null +++ b/e2e/fixture-projects/hardhat-zksync/contracts/Box.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.16; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +contract Box is Initializable { + uint256 private value; + uint256 private secondValue; + uint256 private thirdValue; + + function initialize(uint256 initValue) public initializer { + value = initValue; + } + + // Reads the last stored value + function retrieve() public view returns (uint256) { + return value; + } + + // Stores a new value in the contract + function store(uint256 newValue) public { + value = newValue; + emit ValueChanged(newValue); + } + // Emitted when the stored value changes + event ValueChanged(uint256 newValue); +} diff --git a/e2e/fixture-projects/hardhat-zksync/contracts/BoxUups.sol b/e2e/fixture-projects/hardhat-zksync/contracts/BoxUups.sol new file mode 100644 index 000000000..0b3345dd7 --- /dev/null +++ b/e2e/fixture-projects/hardhat-zksync/contracts/BoxUups.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.16; +import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol'; +import '@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol'; +import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol'; + +contract BoxUups is Initializable, UUPSUpgradeable, OwnableUpgradeable { + uint256 private value; + uint256 private secondValue; + uint256 private thirdValue; + + function initialize(uint256 initValue) public initializer { + value = initValue; + __Ownable_init(); + __UUPSUpgradeable_init(); + } + + // Reads the last stored value + function retrieve() public view returns (uint256) { + return value; + } + + // Stores a new value in the contract + function store(uint256 newValue) public { + value = newValue; + emit ValueChanged(newValue); + } + + function _authorizeUpgrade(address) internal override onlyOwner {} + + // Emitted when the stored value changes + event ValueChanged(uint256 newValue); +} diff --git a/e2e/fixture-projects/hardhat-zksync/contracts/BoxUupsV2.sol b/e2e/fixture-projects/hardhat-zksync/contracts/BoxUupsV2.sol new file mode 100644 index 000000000..3ae876bf9 --- /dev/null +++ b/e2e/fixture-projects/hardhat-zksync/contracts/BoxUupsV2.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.16; +import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol'; +import '@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol'; +import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol'; + +contract BoxUupsV2 is Initializable, UUPSUpgradeable, OwnableUpgradeable { + uint256 private value; + uint256 private secondValue; + uint256 private thirdValue; + + function initialize(uint256 initValue) public initializer { + value = initValue; + } + + // Reads the last stored value and returns it with a prefix + function retrieve() public view returns (string memory) { + return string(abi.encodePacked('V2: ', uint2str(value))); + } + + // Converts a uint to a string + function uint2str(uint _i) internal pure returns (string memory) { + if (_i == 0) { + return '0'; + } + uint j = _i; + uint len; + while (j != 0) { + len++; + j /= 10; + } + bytes memory bstr = new bytes(len); + uint k = len; + while (_i != 0) { + k = k - 1; + uint8 temp = (48 + uint8(_i - (_i / 10) * 10)); + bytes1 b1 = bytes1(temp); + bstr[k] = b1; + _i /= 10; + } + return string(bstr); + } + + // Stores a new value in the contract + function store(uint256 newValue) public { + value = newValue; + emit ValueChanged(newValue); + } + + function _authorizeUpgrade(address) internal override onlyOwner {} + + // Emitted when the stored value changes + event ValueChanged(uint256 newValue); +} diff --git a/e2e/fixture-projects/hardhat-zksync/contracts/BoxV2.sol b/e2e/fixture-projects/hardhat-zksync/contracts/BoxV2.sol new file mode 100644 index 000000000..d451cb8d7 --- /dev/null +++ b/e2e/fixture-projects/hardhat-zksync/contracts/BoxV2.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.16; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +contract BoxV2 is Initializable{ + uint256 private value; + uint256 private secondValue; + uint256 private thirdValue; + + // Emitted when the stored value changes + event ValueChanged(uint256 newValue); + + function initialize(uint256 initValue) public initializer { + value = initValue; + } + + // Stores a new value in the contract + function store(uint256 newValue) public { + value = newValue; + emit ValueChanged(newValue); + } + + // Reads the last stored value and returns it with a prefix + function retrieve() public view returns (string memory) { + return string(abi.encodePacked("V2: ", uint2str(value))); + } + + // Converts a uint to a string + function uint2str(uint _i) internal pure returns (string memory) { + if (_i == 0) { + return "0"; + } + uint j = _i; + uint len; + while (j != 0) { + len++; + j /= 10; + } + bytes memory bstr = new bytes(len); + uint k = len; + while (_i != 0) { + k = k - 1; + uint8 temp = (48 + uint8(_i - (_i / 10) * 10)); + bytes1 b1 = bytes1(temp); + bstr[k] = b1; + _i /= 10; + } + return string(bstr); + } +} diff --git a/e2e/fixture-projects/hardhat-zksync/contracts/Empty.sol b/e2e/fixture-projects/hardhat-zksync/contracts/Empty.sol new file mode 100644 index 000000000..8008f720f --- /dev/null +++ b/e2e/fixture-projects/hardhat-zksync/contracts/Empty.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; +pragma abicoder v2; + +contract Empty { +} diff --git a/e2e/fixture-projects/hardhat-zksync/contracts/Foo.sol b/e2e/fixture-projects/hardhat-zksync/contracts/Foo.sol new file mode 100644 index 000000000..f821b36c0 --- /dev/null +++ b/e2e/fixture-projects/hardhat-zksync/contracts/Foo.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; +pragma abicoder v2; + +contract Foo { + string public name = "Foo"; +} diff --git a/e2e/fixture-projects/hardhat-zksync/contracts/Greeter.sol b/e2e/fixture-projects/hardhat-zksync/contracts/Greeter.sol new file mode 100644 index 000000000..f502c1ad1 --- /dev/null +++ b/e2e/fixture-projects/hardhat-zksync/contracts/Greeter.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; +pragma abicoder v2; + +contract Greeter { + string greeting; + constructor(string memory _greeting) { + greeting = _greeting; + } + + function greet() public view returns (string memory) { + return greeting; + } + + function setGreeting(string memory _greeting) public { + greeting = _greeting; + } +} diff --git a/e2e/fixture-projects/hardhat-zksync/contracts/Import.sol b/e2e/fixture-projects/hardhat-zksync/contracts/Import.sol new file mode 100644 index 000000000..f687d696b --- /dev/null +++ b/e2e/fixture-projects/hardhat-zksync/contracts/Import.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; +pragma abicoder v2; + +// import Foo.sol from current directory +import "./Bar.sol"; + +contract Import { + // Initialize Bar + Bar public bar = new Bar(); + + // Test Foo.sol by getting it's name. + function getFooName() public view returns (string memory) { + return bar.foo().name(); + } +} diff --git a/e2e/fixture-projects/hardhat-zksync/contracts/TwoUserMultisig.sol b/e2e/fixture-projects/hardhat-zksync/contracts/TwoUserMultisig.sol new file mode 100644 index 000000000..bdad11e4a --- /dev/null +++ b/e2e/fixture-projects/hardhat-zksync/contracts/TwoUserMultisig.sol @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IAccount.sol"; +import "@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol"; + +import "@openzeppelin/contracts/interfaces/IERC1271.sol"; + +// Used for signature validation +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +// Access zkSync system contracts, in this case for nonce validation vs NONCE_HOLDER_SYSTEM_CONTRACT +import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; +// to call non-view method of system contracts +import "@matterlabs/zksync-contracts/l2/system-contracts/libraries/SystemContractsCaller.sol"; + +contract TwoUserMultisig is IAccount, IERC1271 { + // to get transaction hash + using TransactionHelper for Transaction; + + // state variables for account owners + address public owner1; + address public owner2; + + bytes4 constant EIP1271_SUCCESS_RETURN_VALUE = 0x1626ba7e; + + modifier onlyBootloader() { + require( + msg.sender == BOOTLOADER_FORMAL_ADDRESS, + "Only bootloader can call this method" + ); + // Continue execution if called from the bootloader. + _; + } + + constructor(address _owner1, address _owner2) { + owner1 = _owner1; + owner2 = _owner2; + } + + function validateTransaction( + bytes32, + bytes32 _suggestedSignedHash, + Transaction calldata _transaction + ) external payable override onlyBootloader returns (bytes4 magic) { + magic = _validateTransaction(_suggestedSignedHash, _transaction); + } + + function _validateTransaction( + bytes32 _suggestedSignedHash, + Transaction calldata _transaction + ) internal returns (bytes4 magic) { + // Incrementing the nonce of the account. + // Note, that reserved[0] by convention is currently equal to the nonce passed in the transaction + SystemContractsCaller.systemCallWithPropagatedRevert( + uint32(gasleft()), + address(NONCE_HOLDER_SYSTEM_CONTRACT), + 0, + abi.encodeCall(INonceHolder.incrementMinNonceIfEquals, (_transaction.nonce)) + ); + + bytes32 txHash; + // While the suggested signed hash is usually provided, it is generally + // not recommended to rely on it to be present, since in the future + // there may be tx types with no suggested signed hash. + if (_suggestedSignedHash == bytes32(0)) { + txHash = _transaction.encodeHash(); + } else { + txHash = _suggestedSignedHash; + } + + // The fact there is are enough balance for the account + // should be checked explicitly to prevent user paying for fee for a + // transaction that wouldn't be included on Ethereum. + uint256 totalRequiredBalance = _transaction.totalRequiredBalance(); + require(totalRequiredBalance <= address(this).balance, "Not enough balance for fee + value"); + + if (isValidSignature(txHash, _transaction.signature) == EIP1271_SUCCESS_RETURN_VALUE) { + magic = ACCOUNT_VALIDATION_SUCCESS_MAGIC; + } + } + + function executeTransaction( + bytes32, + bytes32, + Transaction calldata _transaction + ) external payable override onlyBootloader { + _executeTransaction(_transaction); + } + + function _executeTransaction(Transaction calldata _transaction) internal { + address to = address(uint160(_transaction.to)); + uint128 value = Utils.safeCastToU128(_transaction.value); + bytes memory data = _transaction.data; + + if (to == address(DEPLOYER_SYSTEM_CONTRACT)) { + uint32 gas = Utils.safeCastToU32(gasleft()); + + // Note, that the deployer contract can only be called + // with a "systemCall" flag. + SystemContractsCaller.systemCallWithPropagatedRevert(gas, to, value, data); + } else { + bool success; + assembly { + success := call(gas(), to, value, add(data, 0x20), mload(data), 0, 0) + } + require(success); + } + } + + function executeTransactionFromOutside(Transaction calldata _transaction) + external + payable + { + _validateTransaction(bytes32(0), _transaction); + _executeTransaction(_transaction); + } + + function isValidSignature(bytes32 _hash, bytes memory _signature) + public + view + override + returns (bytes4 magic) + { + magic = EIP1271_SUCCESS_RETURN_VALUE; + + if (_signature.length != 130) { + // Signature is invalid anyway, but we need to proceed with the signature verification as usual + // in order for the fee estimation to work correctly + _signature = new bytes(130); + + // Making sure that the signatures look like a valid ECDSA signature and are not rejected rightaway + // while skipping the main verification process. + _signature[64] = bytes1(uint8(27)); + _signature[129] = bytes1(uint8(27)); + } + + (bytes memory signature1, bytes memory signature2) = extractECDSASignature(_signature); + + if(!checkValidECDSASignatureFormat(signature1) || !checkValidECDSASignatureFormat(signature2)) { + magic = bytes4(0); + } + + address recoveredAddr1 = ECDSA.recover(_hash, signature1); + address recoveredAddr2 = ECDSA.recover(_hash, signature2); + + // Note, that we should abstain from using the require here in order to allow for fee estimation to work + if(recoveredAddr1 != owner1 || recoveredAddr2 != owner2) { + magic = bytes4(0); + } + } + + // This function verifies that the ECDSA signature is both in correct format and non-malleable + function checkValidECDSASignatureFormat(bytes memory _signature) internal pure returns (bool) { + if(_signature.length != 65) { + return false; + } + + uint8 v; + bytes32 r; + bytes32 s; + // Signature loading code + // we jump 32 (0x20) as the first slot of bytes contains the length + // we jump 65 (0x41) per signature + // for v we load 32 bytes ending with v (the first 31 come from s) then apply a mask + assembly { + r := mload(add(_signature, 0x20)) + s := mload(add(_signature, 0x40)) + v := and(mload(add(_signature, 0x41)), 0xff) + } + if(v != 27 && v != 28) { + return false; + } + + // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature + // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines + // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most + // signatures from current libraries generate a unique signature with an s-value in the lower half order. + // + // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value + // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or + // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept + // these malleable signatures as well. + if(uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { + return false; + } + + return true; + } + + function extractECDSASignature(bytes memory _fullSignature) internal pure returns (bytes memory signature1, bytes memory signature2) { + require(_fullSignature.length == 130, "Invalid length"); + + signature1 = new bytes(65); + signature2 = new bytes(65); + + // Copying the first signature. Note, that we need an offset of 0x20 + // since it is where the length of the `_fullSignature` is stored + assembly { + let r := mload(add(_fullSignature, 0x20)) + let s := mload(add(_fullSignature, 0x40)) + let v := and(mload(add(_fullSignature, 0x41)), 0xff) + + mstore(add(signature1, 0x20), r) + mstore(add(signature1, 0x40), s) + mstore8(add(signature1, 0x60), v) + } + + // Copying the second signature. + assembly { + let r := mload(add(_fullSignature, 0x61)) + let s := mload(add(_fullSignature, 0x81)) + let v := and(mload(add(_fullSignature, 0x82)), 0xff) + + mstore(add(signature2, 0x20), r) + mstore(add(signature2, 0x40), s) + mstore8(add(signature2, 0x60), v) + } + } + + function payForTransaction( + bytes32, + bytes32, + Transaction calldata _transaction + ) external payable override onlyBootloader { + bool success = _transaction.payToTheBootloader(); + require(success, "Failed to pay the fee to the operator"); + } + + function prepareForPaymaster( + bytes32, // _txHash + bytes32, // _suggestedSignedHash + Transaction calldata _transaction + ) external payable override onlyBootloader { + _transaction.processPaymasterInput(); + } + + fallback() external { + // fallback of default account shouldn't be called by bootloader under no circumstances + assert(msg.sender != BOOTLOADER_FORMAL_ADDRESS); + + // If the contract is called directly, behave like an EOA + } + + receive() external payable { + // If the contract is called directly, behave like an EOA. + // Note, that is okay if the bootloader sends funds with no calldata as it may be used for refunds/operator payments + } +} \ No newline at end of file diff --git a/e2e/fixture-projects/hardhat-zksync/contracts/custom_paymaster/paymaster/Paymaster.sol b/e2e/fixture-projects/hardhat-zksync/contracts/custom_paymaster/paymaster/Paymaster.sol new file mode 100644 index 000000000..e1481d9f8 --- /dev/null +++ b/e2e/fixture-projects/hardhat-zksync/contracts/custom_paymaster/paymaster/Paymaster.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IPaymaster, ExecutionResult, PAYMASTER_VALIDATION_SUCCESS_MAGIC} from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymaster.sol"; +import {IPaymasterFlow} from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymasterFlow.sol"; +import {TransactionHelper, Transaction} from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol"; + +import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +/// @author Matter Labs +/// @notice This smart contract pays the gas fees for accounts with balance of a specific ERC20 token. It makes use of the approval-based flow paymaster. +contract ApprovalPaymaster is IPaymaster, Ownable { + uint256 constant PRICE_FOR_PAYING_FEES = 1; + + address public allowedToken; + + modifier onlyBootloader() { + require( + msg.sender == BOOTLOADER_FORMAL_ADDRESS, + "Only bootloader can call this method" + ); + // Continue execution if called from the bootloader. + _; + } + + constructor(address _erc20) { + allowedToken = _erc20; + } + + function validateAndPayForPaymasterTransaction( + bytes32, + bytes32, + Transaction calldata _transaction + ) + external + payable + onlyBootloader + returns (bytes4 magic, bytes memory context) + { + // By default we consider the transaction as accepted. + magic = PAYMASTER_VALIDATION_SUCCESS_MAGIC; + require( + _transaction.paymasterInput.length >= 4, + "The standard paymaster input must be at least 4 bytes long" + ); + + bytes4 paymasterInputSelector = bytes4( + _transaction.paymasterInput[0:4] + ); + // Approval based flow + if (paymasterInputSelector == IPaymasterFlow.approvalBased.selector) { + // While the transaction data consists of address, uint256 and bytes data, + // the data is not needed for this paymaster + (address token, uint256 amount, bytes memory data) = abi.decode( + _transaction.paymasterInput[4:], + (address, uint256, bytes) + ); + + // Verify if token is the correct one + require(token == allowedToken, "Invalid token"); + + // We verify that the user has provided enough allowance + address userAddress = address(uint160(_transaction.from)); + + address thisAddress = address(this); + + uint256 providedAllowance = IERC20(token).allowance( + userAddress, + thisAddress + ); + require( + providedAllowance >= PRICE_FOR_PAYING_FEES, + "Min allowance too low" + ); + + // Note, that while the minimal amount of ETH needed is tx.gasPrice * tx.gasLimit, + // neither paymaster nor account are allowed to access this context variable. + uint256 requiredETH = _transaction.gasLimit * + _transaction.maxFeePerGas; + + try + IERC20(token).transferFrom(userAddress, thisAddress, amount) + {} catch (bytes memory revertReason) { + // If the revert reason is empty or represented by just a function selector, + // we replace the error with a more user-friendly message + if (revertReason.length <= 4) { + revert("Failed to transferFrom from users' account"); + } else { + assembly { + revert(add(0x20, revertReason), mload(revertReason)) + } + } + } + + // The bootloader never returns any data, so it can safely be ignored here. + (bool success, ) = payable(BOOTLOADER_FORMAL_ADDRESS).call{ + value: requiredETH + }(""); + require( + success, + "Failed to transfer tx fee to the bootloader. Paymaster balance might not be enough." + ); + } else { + revert("Unsupported paymaster flow"); + } + } + + function postTransaction( + bytes calldata _context, + Transaction calldata _transaction, + bytes32, + bytes32, + ExecutionResult _txResult, + uint256 _maxRefundedGas + ) external payable override onlyBootloader {} + + function withdraw(address _to) external onlyOwner { + (bool success, ) = payable(_to).call{value: address(this).balance}(""); + require(success, "Failed to withdraw funds from paymaster."); + } + + receive() external payable {} +} diff --git a/e2e/fixture-projects/hardhat-zksync/contracts/custom_paymaster/token/Token.sol b/e2e/fixture-projects/hardhat-zksync/contracts/custom_paymaster/token/Token.sol new file mode 100644 index 000000000..cea8272a5 --- /dev/null +++ b/e2e/fixture-projects/hardhat-zksync/contracts/custom_paymaster/token/Token.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract Token is ERC20 { + uint8 private _decimals; + + constructor( + string memory name_, + string memory symbol_, + uint8 decimals_ + ) ERC20(name_, symbol_) { + _decimals = decimals_; + } + + function mint(address _to, uint256 _amount) public returns (bool) { + _mint(_to, _amount); + return true; + } + + function decimals() public view override returns (uint8) { + return _decimals; + } +} \ No newline at end of file diff --git a/e2e/fixture-projects/hardhat-zksync/contracts/erc20/MyERC20Token.sol b/e2e/fixture-projects/hardhat-zksync/contracts/erc20/MyERC20Token.sol new file mode 100644 index 000000000..b97dfc0d2 --- /dev/null +++ b/e2e/fixture-projects/hardhat-zksync/contracts/erc20/MyERC20Token.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; + +/** + * @title MyERC20Token + * @dev This is a basic ERC20 token using the OpenZeppelin's ERC20PresetFixedSupply preset. + * You can edit the default values as needed. + */ +contract MyERC20Token is ERC20Burnable { + + /** + * @dev Constructor to initialize the token with default values. + * You can edit these values as needed. + */ + constructor() ERC20("DefaultTokenName", "DTN") { + // Default initial supply of 1 million tokens (with 18 decimals) + uint256 initialSupply = 1_000_000 * (10 ** 18); + + // The initial supply is minted to the deployer's address + _mint(msg.sender, initialSupply); + } + + // Additional functions or overrides can be added here if needed. +} diff --git a/e2e/fixture-projects/hardhat-zksync/hardhat.config.ts b/e2e/fixture-projects/hardhat-zksync/hardhat.config.ts new file mode 100644 index 000000000..5bd775106 --- /dev/null +++ b/e2e/fixture-projects/hardhat-zksync/hardhat.config.ts @@ -0,0 +1,38 @@ +import '@matterlabs/hardhat-zksync'; + +import { HardhatUserConfig } from 'hardhat/config'; + +const config: HardhatUserConfig = { + zksolc: { + version: 'latest', + compilerSource: 'binary', + settings: { + isSystem: true, + optimizer: { + enabled: true, + }, + }, + }, + defaultNetwork:'zkSyncNetwork', + networks: { + hardhat: { + zksync: true, + }, + eth: { + zksync: true, + url: 'http://localhost:8545', + }, + zkSyncNetwork: { + zksync: true, + ethNetwork: 'eth', + url: 'http://localhost:3050', + forceDeploy:true, + accounts:["0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110"] + }, + }, + solidity: { + version: '0.8.20', + }, +}; + +export default config; diff --git a/e2e/fixture-projects/hardhat-zksync/preprocess.sh b/e2e/fixture-projects/hardhat-zksync/preprocess.sh new file mode 100755 index 000000000..356596c64 --- /dev/null +++ b/e2e/fixture-projects/hardhat-zksync/preprocess.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +# Set script to exit on error +set -e + +echo "Current directory before any operations: $(pwd)" + diff --git a/e2e/fixture-projects/hardhat-zksync/test-account-abstraction.sh b/e2e/fixture-projects/hardhat-zksync/test-account-abstraction.sh new file mode 100644 index 000000000..4da4660fd --- /dev/null +++ b/e2e/fixture-projects/hardhat-zksync/test-account-abstraction.sh @@ -0,0 +1,11 @@ +set -e + +two_user_multisig_logs=$(pnpm hardhat deploy-zksync:contract --contract-name TwoUserMultisig "0xbd29A1B981925B94eEc5c4F1125AF02a2Ec4d1cA" "0x4F9133D1d3F50011A6859807C837bdCB31Aaab13" --deployment-type createAccount --no-compile 2>&1) + +multisig_address=$(echo "$two_user_multisig_logs" | grep "Contract TwoUserMultisig deployed at" | awk '{print $NF}') + +if [ -n "$multisig_address" ]; then + echo "TwoUserMultisig contract was deployed at: $multisig_address" +else + echo "Failed to find the TwoUserMultisig contract deployment address in the output." +fi \ No newline at end of file diff --git a/e2e/fixture-projects/hardhat-zksync/test-factory.sh b/e2e/fixture-projects/hardhat-zksync/test-factory.sh new file mode 100644 index 000000000..2dd3c1b56 --- /dev/null +++ b/e2e/fixture-projects/hardhat-zksync/test-factory.sh @@ -0,0 +1,12 @@ +set -e + +import_output=$(pnpm hardhat deploy-zksync:contract --contract-name Import --deployment-type create --no-compile 2>&1) + +import_contract_address=$(echo "$import_output" | grep "Contract Import deployed at" | awk '{print $NF}') + +# Check and echo the Import contract address +if [ -n "$import_contract_address" ]; then + echo "Import contract was deployed at: $import_contract_address" +else + echo "Failed to find the Import contract deployment address in the output." +fi \ No newline at end of file diff --git a/e2e/fixture-projects/hardhat-zksync/test-paymaster.sh b/e2e/fixture-projects/hardhat-zksync/test-paymaster.sh new file mode 100644 index 000000000..bf5e6a3f8 --- /dev/null +++ b/e2e/fixture-projects/hardhat-zksync/test-paymaster.sh @@ -0,0 +1,23 @@ +set -e + +token_output=$(pnpm hardhat deploy-zksync:contract --contract-name Token "Ducat" "DCT" 18 --no-compile 2>&1) + +token_address=$(echo "$token_output" | grep "Contract Token deployed at" | awk '{print $NF}') + +# Check and echo the token contract address +if [ -n "$token_address" ]; then + echo "Token contract was deployed at: $token_address" +else + echo "Failed to find the Token contract deployment address in the output." +fi + + +paymaster_output=$(pnpm hardhat deploy-zksync:contract --contract-name ApprovalPaymaster $token_address --no-compile --deployment-type createAccount 2>&1) + +paymaster_address=$(echo "$paymaster_output" | grep "Contract ApprovalPaymaster deployed at" | awk '{print $NF}') + +if [ -n "$paymaster_address" ]; then + echo "ApprovalPaymaster contract was deployed at: $paymaster_address" +else + echo "Failed to find the ApprovalPaymaster contract deployment address in the output." +fi \ No newline at end of file diff --git a/e2e/fixture-projects/hardhat-zksync/test-upgradable-beacon.sh b/e2e/fixture-projects/hardhat-zksync/test-upgradable-beacon.sh new file mode 100644 index 000000000..493dafb4f --- /dev/null +++ b/e2e/fixture-projects/hardhat-zksync/test-upgradable-beacon.sh @@ -0,0 +1,48 @@ +set -e + +rm -r .upgradable + +beacon_output=$(pnpm hardhat deploy-zksync:beacon --contract-name Box 42 --deployment-type create --initializer initialize --no-compile 2>&1) + +echo "$beacon_output" + +# Extract deployment addresses +beacon_impl_address=$(echo "$beacon_output" | grep "Beacon impl deployed at" | awk '{print $NF}') +beacon_address=$(echo "$beacon_output" | grep "Beacon deployed at" | awk '{print $NF}') +beacon_proxy_address=$(echo "$beacon_output" | grep "Beacon proxy deployed at" | awk '{print $NF}') + +# Check and echo the beacon implementation address +if [ -n "$beacon_impl_address" ]; then + echo "Beacon implementation was deployed at: $beacon_impl_address" +else + echo "Failed to find the Beacon implementation deployment address in the output." +fi + +# Check and echo the beacon address +if [ -n "$beacon_address" ]; then + echo "Beacon was deployed at: $beacon_address" +else + echo "Failed to find the Beacon deployment address in the output." +fi + +# Check and echo the beacon proxy address +if [ -n "$beacon_proxy_address" ]; then + echo "Beacon proxy was deployed at: $beacon_proxy_address" +else + echo "Failed to find the Beacon proxy deployment address in the output." +fi + +echo "---Upgrade Beacon---" + +upgrade_output=$(yarn hardhat upgrade-zksync:beacon --contract-name BoxV2 --beacon-address "$beacon_address" --no-compile 2>&1) + +echo "$upgrade_output" + +new_contract_address=$(echo "$upgrade_output" | grep "New beacon impl deployed at" | awk '{print $6}') + +# Check and echo the new contract address +if [ -n "$new_contract_address" ]; then + echo "Upgrade was successful. New contract address: $new_contract_address" +else + echo "Failed to find the new contract address in the output." +fi \ No newline at end of file diff --git a/e2e/fixture-projects/hardhat-zksync/test-upgradable-proxy-uups.sh b/e2e/fixture-projects/hardhat-zksync/test-upgradable-proxy-uups.sh new file mode 100644 index 000000000..db6c98e1e --- /dev/null +++ b/e2e/fixture-projects/hardhat-zksync/test-upgradable-proxy-uups.sh @@ -0,0 +1,39 @@ +set -e + +rm -r .upgradable + +proxy_output=$(pnpm hardhat deploy-zksync:proxy --contract-name BoxUups 42 --deployment-type create --initializer initialize --no-compile 2>&1) + +echo "$proxy_output" + +implementation_address=$(echo "$proxy_output" | grep "Implementation contract was deployed to" | awk '{print $NF}') +uups_proxy_address=$(echo "$proxy_output" | grep "UUPS proxy was deployed to" | awk '{print $NF}') + +# Check and echo the implementation contract address +if [ -n "$implementation_address" ]; then + echo "Implementation contract was deployed at: $implementation_address" +else + echo "Failed to find the Implementation contract deployment address in the output." +fi + +# Check and echo the UUPS proxy address +if [ -n "$uups_proxy_address" ]; then + echo "UUPS proxy was deployed at: $uups_proxy_address" +else + echo "Failed to find the UUPS proxy deployment address in the output." +fi + +echo "---Upgrade---" + +upgrade_output=$(yarn hardhat upgrade-zksync:proxy --contract-name BoxUupsV2 --proxy-address "$uups_proxy_address" --no-compile 2>&1) + +echo "$upgrade_output" + +new_contract_address=$(echo "$upgrade_output" | grep "Contract successfully upgraded to" | awk '{print $5}') + +# Check and echo the new contract address +if [ -n "$new_contract_address" ]; then + echo "Upgrade was successful. New contract address: $new_contract_address" +else + echo "Failed to find the new contract address in the upgrade output." +fi \ No newline at end of file diff --git a/e2e/fixture-projects/hardhat-zksync/test-upgradable-proxy.sh b/e2e/fixture-projects/hardhat-zksync/test-upgradable-proxy.sh new file mode 100644 index 000000000..9a4cd77f9 --- /dev/null +++ b/e2e/fixture-projects/hardhat-zksync/test-upgradable-proxy.sh @@ -0,0 +1,43 @@ +set -e + +proxy_output=$(pnpm hardhat deploy-zksync:proxy --contract-name Box 42 --deployment-type create --initializer initialize --no-compile 2>&1) + +# Extract deployment addresses +implementation_address=$(echo "$proxy_output" | grep "Implementation contract was deployed to" | awk '{print $NF}') +admin_address=$(echo "$proxy_output" | grep "Admin was deployed to" | awk '{print $NF}') +proxy_address=$(echo "$proxy_output" | grep "Transparent proxy was deployed to" | awk '{print $NF}') + +# Check and echo the implementation address +if [ -n "$implementation_address" ]; then + echo "Implementation contract was deployed at: $implementation_address" +else + echo "Failed to find the Implementation contract deployment address in the output." +fi + +# Check and echo the admin address +if [ -n "$admin_address" ]; then + echo "Admin was deployed at: $admin_address" +else + echo "Failed to find the Admin deployment address in the output." +fi + +# Check and echo the proxy address +if [ -n "$proxy_address" ]; then + echo "Transparent proxy was deployed at: $proxy_address" +else + echo "Failed to find the Transparent proxy deployment address in the output." +fi + +echo "---Upgrade Proxy---" + +upgrade_output=$(yarn hardhat upgrade-zksync:proxy --contract-name BoxV2 --proxy-address "$proxy_address" --no-compile 2>&1) + +new_contract_address=$(echo "$upgrade_output" | grep "Contract successfully upgraded to" | awk '{print $5}') +transaction_id=$(echo "$upgrade_output" | grep "with tx" | awk '{print $8}') + +# Check and echo the new contract address +if [ -n "$new_contract_address" ]; then + echo "Upgrade was successful. New contract address: $new_contract_address" +else + echo "Failed to find the new contract address in the output." +fi \ No newline at end of file diff --git a/e2e/fixture-projects/hardhat-zksync/test.sh b/e2e/fixture-projects/hardhat-zksync/test.sh new file mode 100644 index 000000000..d0b2c7c64 --- /dev/null +++ b/e2e/fixture-projects/hardhat-zksync/test.sh @@ -0,0 +1,63 @@ +#! /usr/bin/env sh + +set -e + +. ../../helpers.sh + +echo "Running tests: $(basename "$(pwd)")" + +pnpm install @openzeppelin/contracts@4.9.2 +pnpm install @openzeppelin/contracts-upgradeable@4.9.2 +pnpm install @matterlabs/hardhat-zksync +pnpm install hardhat@2.19.4 +pnpm install @matterlabs/zksync-contracts +pnpm install typescript@5.1.6 +pnpm install ts-node@10.6.0 + +pnpm hardhat deploy-zksync:contract --contract-name Greeter "Hi there!" + + +greeter_logs=$(echo "$greeter_output" | grep "Contract Greeter deployed at" | awk '{print $NF}') + +# Check if we found the deployment address +if [ -n "$greeter_logs" ]; then + echo "Greeter contract was deployed at: $greeter_logs" +else + echo "Failed to find the Greeter deployment address in the output." +fi + + +empty_output=$(pnpm hardhat deploy-zksync:contract --contract-name Empty --deployment-type create --no-compile 2>&1) + +empty_logs=$(echo "$empty_output" | grep "Contract Empty deployed at" | awk '{print $NF}') + + +if [ -n "$empty_logs" ]; then + echo "Empty contract was deployed at: $empty_logs" +else + echo "Failed to find the Empty deployment address in the output." + echo "$empty_output" +fi + +chmod +x ./test-account-abstraction.sh +chmod +x ./test-factory.sh +chmod +x ./test-paymaster.sh +chmod +x ./test-upgradable-proxy.sh +chmod +x ./test-upgradable-proxy-uups.sh +chmod +x ./test-upgradable-beacon.sh + + +./test-account-abstraction.sh +echo "---------------" +./test-factory.sh +echo "---------------" +./test-paymaster.sh +echo "---------------" +./test-upgradable-proxy.sh +echo "---------------" +./test-upgradable-beacon.sh +echo "---------------" +./test-upgradable-proxy-uups.sh +echo "---------------" + + diff --git a/e2e/fixture-projects/mixed/preprocess.sh b/e2e/fixture-projects/mixed/preprocess.sh index 8d92ee88e..1e2573a82 100755 --- a/e2e/fixture-projects/mixed/preprocess.sh +++ b/e2e/fixture-projects/mixed/preprocess.sh @@ -38,10 +38,10 @@ cat < "$SCRIPT_DIR/package.json" "typescript": "^5.1.6" }, "dependencies": { - "@matterlabs/hardhat-zksync-deploy": "1.2.1", - "@matterlabs/hardhat-zksync-solc": "1.1.4", - "@matterlabs/hardhat-zksync-node":"1.0.2", - "@matterlabs/hardhat-zksync-upgradable":"1.3.1", + "@matterlabs/hardhat-zksync-deploy": "^1.3.0", + "@matterlabs/hardhat-zksync-solc": "^1.1.4", + "@matterlabs/hardhat-zksync-node":"^1.0.3", + "@matterlabs/hardhat-zksync-upgradable":"^1.4.0", "@matterlabs/hardhat-zksync-vyper": "1.0.8", "@nomiclabs/hardhat-vyper": "^3.0.5", "chalk": "4.1.2", diff --git a/e2e/run-fixture-projects.sh b/e2e/run-fixture-projects.sh index 6b503d8ff..32fe4f92d 100755 --- a/e2e/run-fixture-projects.sh +++ b/e2e/run-fixture-projects.sh @@ -62,7 +62,7 @@ for dir in "${FIXTURE_PROJECTS_DIR}"/*; do cd "$dir" echo "[e2e] Installing modules in $dir" - pnpm i + pnpm i || { echo "pnpm install failed, initializing pnpm"; pnpm init ; } chmod +x ./test.sh ./test.sh