eth-brownie / brownie

A Python-based development and testing framework for smart contracts targeting the Ethereum Virtual Machine.
https://eth-brownie.readthedocs.io
MIT License
2.64k stars 555 forks source link

Empty Bytes is Converted to Length of 1 #1102

Open PatrickAlphaC opened 3 years ago

PatrickAlphaC commented 3 years ago

Environment information

What was wrong?

When I work with this exact contract:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract A {
    uint256 public length;
    bytes public data;
    constructor(bytes memory _data) public{
        data = _data;
        length=_data.length;
    }

    function store(bytes memory newValue) public {
        data = newValue;
        length = data.length;
    }
}

In remix, and I start with "0x" as the constructor parameter, when I call length I get 0.

However, when I deploy this in Brownie with the following:

from brownie import A, accounts

def main():
    account = accounts[0]
    box = A.deploy("", {"from": account})
    print(box.length())

I get 1

Running the same deployment in hardhat, I get 0:

const { ContractFactory } = require('ethers')

async function main() {
    let Box = await ethers.getContractFactory("Box")
    box = await Box.deploy("0x")
    value = await box.length()
    console.log(value.toString())
}

main()

How can it be fixed?

I'm not sure how this can be fixed yet... I'm still triaging. I'm looking for some way to have a size 0 bytes variable. I am able to do this in hardhat, truffle, and remix.

PatrickAlphaC commented 3 years ago

I found it:

def _to_hex(value: Any) -> str:
    """Convert a value to a hexstring"""
    if isinstance(value, bytes):
        return HexBytes(value).hex()

This is in datatypes.py. The .hex() call is defined as:

def hex(self) -> str:
        """
        Just like :meth:`bytes.hex`, but prepends "0x"
        """
        return "0x" + super().hex()

So it always appends 0x to the start of the bytes. I think a simple "if "0x" -> just return" would fix this.

PatrickAlphaC commented 3 years ago

Right now, my workaround is as follows:

box = A.deploy(eth_utils.to_bytes(hexstr="0x"), {"from": account})

If I want to deploy something with an empty bytes string. But it seems very unintuitive.

iamdefinitelyahuman commented 3 years ago

Ahh that is strange.. yeah we should fix this.

johnbchron commented 1 year ago

Dropping a comment to note that this is still an issue. Spent like 12 hours debugging a signature issue; found that it was a bytes field being given a length of 1.