Skip to content

bytes public data getter loops forever for equivalence checking #697

Open
@msooseth

Description

@msooseth

For a trivial contract:

contract A {
    bytes public data;
    function stuff(uint a, uint b) public pure returns (uint) {
       unchecked {
         uint c = a + b/2;
         return c;
       }
    }
}

we loop forever when we want to check equivalence:

$EXPSOLC --bin-runtime --via-ir --optimize --ssa-cfg-codegen $FILE | tail -n1 > with.s
$EXPSOLC --bin-runtime --via-ir --optimize $FILE | tail -n1 > without.s
cabal run exe:hevm -- equivalence --code-a-file with.s --code-b-file without.s

The two contracts are slightly different, so they don't compare byte-for-byte, and the getter for bytes public data loops forever for copying.

To get to the reason, let's compile a much simpler code:

contract A {
    bytes public data;
}

Which , when compiled with: $EXPSOLC --bin-runtime --via-ir --ir --ir-optimized $FILE we get:

            function abi_encode_t_bytes_storage_to_t_bytes_memory_ptr(value, pos) -> ret {
                let slotValue := sload(value)
                let length := extract_byte_array_length(slotValue)
                pos := array_storeLengthForEncoding_t_bytes_memory_ptr(pos, length)
                switch and(slotValue, 1)
                case 0 {
                    // short byte array
                    mstore(pos, and(slotValue, not(0xff)))
                    ret := add(pos, mul(0x20, iszero(iszero(length))))
                }
                case 1 {
                    // long byte array
                    let dataPos := array_dataslot_t_bytes_storage(value)
                    let i := 0
                    for { } lt(i, length) { i := add(i, 0x20) } {
                        mstore(add(pos, i), sload(dataPos))
                        dataPos := add(dataPos, 1)
                    }
                    ret := add(pos, i)
                }
            }

In the yul, which copies the data when data() is called. The actual bytecode is:

6080366004111561000f575b5f5ffd5b5f3560e01c6373d4a13a14610022575f5ffd5b3461000b5736600319015f1361000b575f548060011c60018216801561016557905b60208110821461014d5780845290929190602083018415610131575f94919250906001146100de57919290505b829003601f01601f1916820167ffffffffffffffff8111838210176100c75780604052602081529091516020820181905260408201928192935e8082016040015f9052601f1990601f0116810181900360400190f35b505050634e487b7160e01b5f52604160045260245ffd5b9091925f525f7f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b83821061011b57509091508201602001610071565b8054828601602001526001019060200190610106565b60ff19909216825291925090602090151560051b830101610071565b50505050634e487b7160e01b5f52602260045260245ffd5b90607f1661004456fea2646970667358221220c358b298b021ffba5627329c90c144b0c2f38566274acc652027a14dcbcad16d64736f6c637827302e382e32392d646576656c6f702e323032352e342e322b636f6d6d69742e32653937356361320058

which decompiles to these interesting parts:

[106]  JUMPDEST  
[107]  DUP4  
[108]  DUP3  
[109]  LT  
[10a]  PUSH2  011b
[10d]  JUMPI

[11b]  JUMPDEST  
[11c]  DUP1  
[11d]  SLOAD  
[11e]  DUP3  
[11f]  DUP7  
[120]  ADD  
[121]  PUSH1  20
[123]  ADD  
[124]  MSTORE  
[125]  PUSH1  01
[127]  ADD  
[128]  SWAP1  
[129]  PUSH1  20
[12b]  ADD  
[12c]  SWAP1  
[12d]  PUSH2  0106
[130]  JUMP

where the LT checks the loop condition, and the SLOAD ... MSTORE does the copy.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions