matter-labs / hardhat-zksync

Apache License 2.0
279 stars 227 forks source link

Violated validation rules: Called contract with no code #281

Closed ChiHaoLu closed 1 year ago

ChiHaoLu commented 1 year ago

Hi, I modify and run the multi-sig script in the basic-example:

import * as ethers from "ethers"
import * as zk from "zksync-web3"
import { HardhatRuntimeEnvironment } from "hardhat/types"
import { Deployer } from "@matterlabs/hardhat-zksync-deploy"
import chalk from "chalk"

const EIP1271_MAGIC_BYTES = "0x1626ba7e"

export default async function (hre: HardhatRuntimeEnvironment) {
    console.info(chalk.yellow("Running deploy script for the Account Abstraction"))

    // Initialize an Ethereum wallet.
    const testMnemonic =
        "stuff slice staff easily soup parent arm payment cotton trade scatter struggle"
    const zkWallet = zk.Wallet.fromMnemonic(testMnemonic, "m/44'/60'/0'/0/0")

    // Create deployer objects and load desired artifacts.
    const contractDeployer = new Deployer(hre, zkWallet, "create")
    const aaDeployer = new Deployer(hre, zkWallet, "createAccount")
    const greeterArtifact = await contractDeployer.loadArtifact("Greeter")
    const aaArtifact = await aaDeployer.loadArtifact("MultiSigAccount")

    const provider = aaDeployer.zkWallet.provider

    // Deposit some funds to L2 in order to be able to perform L2 transactions.
    const depositHandle = await contractDeployer.zkWallet.deposit({
        to: contractDeployer.zkWallet.address,
        token: zk.utils.ETH_ADDRESS,
        amount: ethers.utils.parseEther("0.001"),
    })
    await depositHandle.wait()

    const greeterContract = await contractDeployer.deploy(greeterArtifact, ["Hi there!"])

    console.info(chalk.green(`Greeter was deployed to ${greeterContract.address}`))

    // The two owners of the multisig
    const owner1 = zk.Wallet.createRandom()
    const owner2 = zk.Wallet.createRandom()

    const aa = await aaDeployer.deploy(aaArtifact, [owner1.address, owner2.address], undefined, [])

    const multisigAddress = aa.address

    console.info(chalk.green(`Multisig was deployed to ${multisigAddress}`))

    await (
        await contractDeployer.zkWallet.sendTransaction({
            to: multisigAddress,
            // You can increase the amount of ETH sent to the multisig
            value: ethers.utils.parseEther("0.003"),
        })
    ).wait()

    const newGreeting = "Hello!"
    let aaTx = await greeterContract.populateTransaction.setGreeting(newGreeting)

    const gasLimit = await provider.estimateGas(aaTx)
    const gasPrice = await provider.getGasPrice()

    aaTx = {
        ...aaTx,
        from: multisigAddress,
        gasLimit: gasLimit,
        gasPrice: gasPrice,
        chainId: (await provider.getNetwork()).chainId,
        nonce: await provider.getTransactionCount(multisigAddress),
        type: 113,
        customData: {
            gasPerPubdata: zk.utils.DEFAULT_GAS_PER_PUBDATA_LIMIT,
        } as zk.types.Eip712Meta,
        value: ethers.BigNumber.from(0),
    }

    const signedTxHash = zk.EIP712Signer.getSignedDigest(aaTx)

    const signature = ethers.utils.concat([
        // Note, that `signMessage` wouldn't work here, since we don't want
        // the signed hash to be prefixed with `\x19Ethereum Signed Message:\n`
        ethers.utils.joinSignature(owner1._signingKey().signDigest(signedTxHash)),
        ethers.utils.joinSignature(owner2._signingKey().signDigest(signedTxHash)),
    ])

    // Check the Signature is Valid
    const isValidSignature = await aa.isValidSignature(signedTxHash, signature)
    if (isValidSignature == EIP1271_MAGIC_BYTES) {
        console.info(chalk.green(`Signaure is Valid in MultiSigAccount!`))
    } else {
        throw new Error(`Signature is not valid in MultiSigAccount!`)
    }

    aaTx.customData = {
        ...aaTx.customData,
        customSignature: signature,
    }

    console.log(
        `The multisig's nonce before the first tx is ${await provider.getTransactionCount(
            multisigAddress,
        )}`,
    )
    const sentTx = await provider.sendTransaction(zk.utils.serialize(aaTx))
    await sentTx.wait()

    // Checking that the nonce for the account has increased.
    console.log(
        `The multisig's nonce after the first tx is ${await provider.getTransactionCount(
            multisigAddress,
        )}`,
    )

    // Confirm that tx was successful.
    const greetingFromContract = await greeterContract.greet()
    if (greetingFromContract === newGreeting) {
        console.info(chalk.green("Successfully initiated tx from deployed multisig!"))
    } else {
        throw new Error(`Contract said something unexpected: ${greetingFromContract}`)
    }
}

And the contract is:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;

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 MultiSigAccount 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");
        // Continure 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
    }
}

but it return some error:

$ hardhat deploy-zksync --network zkSyncLocal --script multiSigAccountDemo.ts
>
Running deploy script for the Account Abstraction
Greeter was deployed to 0x111C3E89Ce80e62EE88318C2804920D4c96f92bb
Multisig was deployed to 0x4B5DF730c2e6b28E17013A1485E5d9BC41Efe021
Signaure is Valid in MultiSigAccount!
The multisig's nonce before the first tx is 0
An unexpected error occurred:

Error: processing response error (body="{\"jsonrpc\":\"2.0\",\"error\":{\"code\":3,\"message\":\"failed to validate the transaction. reason: Violated validation rules: Called contract with no code: 000000000000000000000000000000000000fff5\",\"data\":\"0x\"},\"id\":83}\n", error={"code":3,"data":"0x"}, requestBody="{\"method\":\"eth_sendRawTransaction\",\"params\":[\"0x71f9013180840ee6b280840ee6b280830249e194111c3e89ce80e62ee88318c2804920d4c96f92bb80b864a41368620000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000648656c6c6f21000000000000000000000000000000000000000000000000000082010e808082010e944b5df730c2e6b28e17013a1485e5d9bc41efe02182c350c0b88222756c15938377898d445c27c67983c8541bb3f5534069ca62fc4be4d3fb579f21041fb682fca3f295c8893fa230d64de6f9bc058e168b8fe4e44000242dfb571cb186feb35ee37819745a39685993becb8ac5b4eed0fbdec8e86c4753da59bf2030dc820773a4845be4a6a6fe769260a3c8cf7dd6a6947698340ecc27845e965b1cc0\"],\"id\":83,\"jsonrpc\":\"2.0\"}", requestMethod="POST", url="http://localhost:3050", code=SERVER_ERROR, version=web/5.7.1)
    at Logger.makeError (/Users/chihaolu/Desktop/zkSync/zkSync-contracts/node_modules/@ethersproject/logger/src.ts/index.ts:269:28)
    at Logger.throwError (/Users/chihaolu/Desktop/zkSync/zkSync-contracts/node_modules/@ethersproject/logger/src.ts/index.ts:281:20)
    at /Users/chihaolu/Desktop/zkSync/zkSync-contracts/node_modules/@ethersproject/web/src.ts/index.ts:341:28
    at step (/Users/chihaolu/Desktop/zkSync/zkSync-contracts/node_modules/@ethersproject/web/lib/index.js:33:23)
    at Object.next (/Users/chihaolu/Desktop/zkSync/zkSync-contracts/node_modules/@ethersproject/web/lib/index.js:14:53)
    at fulfilled (/Users/chihaolu/Desktop/zkSync/zkSync-contracts/node_modules/@ethersproject/web/lib/index.js:5:58)
    at processTicksAndRejections (node:internal/process/task_queues:96:5) {
  reason: 'processing response error',
  code: 'SERVER_ERROR',
  body: '{"jsonrpc":"2.0","error":{"code":3,"message":"failed to validate the transaction. reason: Violated validation rules: Called contract with no code: 000000000000000000000000000000000000fff5","data":"0x"},"id":83}\n',
  error: Error: failed to validate the transaction. reason: Violated validation rules: Called contract with no code: 000000000000000000000000000000000000fff5
      at getResult (/Users/chihaolu/Desktop/zkSync/zkSync-contracts/node_modules/@ethersproject/providers/src.ts/json-rpc-provider.ts:142:28)
      at processJsonFunc (/Users/chihaolu/Desktop/zkSync/zkSync-contracts/node_modules/@ethersproject/web/src.ts/index.ts:383:22)
      at /Users/chihaolu/Desktop/zkSync/zkSync-contracts/node_modules/@ethersproject/web/src.ts/index.ts:320:42
      at step (/Users/chihaolu/Desktop/zkSync/zkSync-contracts/node_modules/@ethersproject/web/lib/index.js:33:23)
      at Object.next (/Users/chihaolu/Desktop/zkSync/zkSync-contracts/node_modules/@ethersproject/web/lib/index.js:14:53)
      at fulfilled (/Users/chihaolu/Desktop/zkSync/zkSync-contracts/node_modules/@ethersproject/web/lib/index.js:5:58)
      at processTicksAndRejections (node:internal/process/task_queues:96:5) {
    code: 3,
    data: '0x'
  },
  requestBody: '{"method":"eth_sendRawTransaction","params":["0x71f9013180840ee6b280840ee6b280830249e194111c3e89ce80e62ee88318c2804920d4c96f92bb80b864a41368620000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000648656c6c6f21000000000000000000000000000000000000000000000000000082010e808082010e944b5df730c2e6b28e17013a1485e5d9bc41efe02182c350c0b88222756c15938377898d445c27c67983c8541bb3f5534069ca62fc4be4d3fb579f21041fb682fca3f295c8893fa230d64de6f9bc058e168b8fe4e44000242dfb571cb186feb35ee37819745a39685993becb8ac5b4eed0fbdec8e86c4753da59bf2030dc820773a4845be4a6a6fe769260a3c8cf7dd6a6947698340ecc27845e965b1cc0"],"id":83,"jsonrpc":"2.0"}',
  requestMethod: 'POST',
  url: 'http://localhost:3050',
  transaction: {
    type: 113,
    nonce: 0,
    maxPriorityFeePerGas: BigNumber { _hex: '0x0ee6b280', _isBigNumber: true },
    maxFeePerGas: BigNumber { _hex: '0x0ee6b280', _isBigNumber: true },
    gasLimit: BigNumber { _hex: '0x0249e1', _isBigNumber: true },
    to: '0x111C3E89Ce80e62EE88318C2804920D4c96f92bb',
    value: BigNumber { _hex: '0x00', _isBigNumber: true },
    data: '0xa41368620000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000648656c6c6f210000000000000000000000000000000000000000000000000000',
    chainId: BigNumber { _hex: '0x010e', _isBigNumber: true },
    from: '0x4B5DF730c2e6b28E17013A1485E5d9BC41Efe021',
    customData: {
      gasPerPubdata: [BigNumber],
      factoryDeps: [],
      customSignature: '0x22756c15938377898d445c27c67983c8541bb3f5534069ca62fc4be4d3fb579f21041fb682fca3f295c8893fa230d64de6f9bc058e168b8fe4e44000242dfb571cb186feb35ee37819745a39685993becb8ac5b4eed0fbdec8e86c4753da59bf2030dc820773a4845be4a6a6fe769260a3c8cf7dd6a6947698340ecc27845e965b1c',
      paymasterParams: undefined
    },
    hash: '0xbde73ef00da6f34d0804585c5bd9e3ae16ac9904ce1b0615db1e36ae3c098551',
    confirmations: 0
  },
  transactionHash: '0xbde73ef00da6f34d0804585c5bd9e3ae16ac9904ce1b0615db1e36ae3c098551'
}
error Command failed with exit code 1.

I have checked the Greeter and MultiSigAccount Contract have been deployed, and the isValidSignature returns are valid in the script, I could not figure out which call contract has no code.


OS - MacOS M2 Pro 2022

Here are my dependencies:

  "dependencies": {
    "@matterlabs/hardhat-zksync-deploy": "^0.6.3",
    "@matterlabs/hardhat-zksync-solc": "^0.3.17",
    "@matterlabs/zksync-contracts": "^0.6.1",
    "@openzeppelin/contracts": "^4.8.3",
    "@openzeppelin/contracts-upgradeable": "^4.8.3",
    "@types/shelljs": "^0.8.11",
    "@typescript-eslint/eslint-plugin": "^5.43.0",
    "@typescript-eslint/parser": "^5.43.0",
    "chalk": "4.1.2",
    "dotenv": "^16.0.3",
    "eslint": "^8.28.0",
    "eslint-config-prettier": "^8.5.0",
    "eslint-plugin-mocha-no-only": "^1.1.1",
    "ethers": "5.7.2",
    "hardhat": "^2.14.0",
    "prettier": "^2.7.1",
    "prettier-plugin-solidity": "^1.0.0",
    "prompts": "^2.4.2",
    "yargs": "^17.7.2",
    "zksync-web3": "^0.14.3"
  }
caffeinum commented 1 year ago

I have run into the same problem. 0x0....0fff5 is SYSTEM_CALL_CALL_ADDRESS from @matterlabs/zksync-contracts/l2/system-contracts/libraries/SystemContractsCaller.sol:

address constant SYSTEM_CALL_CALL_ADDRESS = address((1 << 16) - 11);

Most probably you've forgot to compile your factory with --is-system flag, so it doesn't recognize this address as a system address

PatrickAlphaC commented 4 months ago

I also got this error, this thread helped me because I am 100% passing the is-system flag in my foundry.toml. I think even with this flag it's not compiling with is-system on because I keep getting this error.

EDIT: Figured it out, the feature was removed from foundry-zksync. Probably a bug.