1

My solidity code example:

// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.2;

library TestLibrary { function sz_varint(uint256 i) internal pure returns (uint256) { uint256 count = 1; assembly { i := shr(7, i) for {} gt(i, 0) {} { i := shr(7, i) count := add(count, 1) } } return count; }

function encode_varint_assembly(uint256 x, uint256 p, bytes memory bs) internal pure returns (uint256) {
    uint256 sz = 0;
    assembly {
        let bsptr := add(bs, p)
        let byt := and(x, 0x7f)
        for {} gt(shr(7, x), 0) {} {
            mstore8(bsptr, or(0x80, byt))
            bsptr := add(bsptr, 1)
            sz := add(sz, 1)
            x := shr(7, x)
            byt := and(x, 0x7f)
        }
        mstore8(bsptr, byt)
        sz := add(sz, 1)
    }
    return sz;
}
function encode_varint_assembly_nomstore8(uint256 x, uint256 p, bytes memory bs) internal pure returns (uint256) {
    uint256 sz = 0;
    assembly {
        let bsptr := add(bs, p)
        let byt := and(x, 0x7f)
        for {} gt(shr(7, x), 0) {} {
            //mstore8(bsptr, or(0x80, byt))
            bsptr := add(bsptr, 1)
            sz := add(sz, 1)
            x := shr(7, x)
            byt := and(x, 0x7f)
        }
        //mstore8(bsptr, byt)
        sz := add(sz, 1)
    }
    return sz;
}
function encode_varint(uint256 x, uint256 p, bytes memory bs) internal pure returns (uint256) {
    uint256 tmp = x;
    uint256 idx = p;
    bytes1 byt = bytes1(uint8(tmp & 0x7f));
    while (tmp > 0x7f) {
        bs[idx] = byt | 0x80;
        tmp = tmp >> 7;
        byt = bytes1(uint8(tmp & 0x7f));
        idx += 1;
    }
    bs[idx] = byt;
    return idx - p + 1;
}

}

contract TestContract { function encode_varint_assembly(uint256 x) public pure returns(bytes memory) { uint256 sz = TestLibrary.sz_varint(x); bytes memory buffer = new bytes(sz); TestLibrary.encode_varint_assembly(x, 0, buffer); return buffer; } function encode_varint_assembly_nomstore8(uint256 x) public pure returns(bytes memory) { uint256 sz = TestLibrary.sz_varint(x); bytes memory buffer = new bytes(sz); TestLibrary.encode_varint_assembly_nomstore8(x, 0, buffer); return buffer; } function encode_varint(uint256 x) public pure returns(bytes memory) { uint256 sz = TestLibrary.sz_varint(x); bytes memory buffer = new bytes(sz); TestLibrary.encode_varint(x, 0, buffer); return buffer; } }

try running some unit tests in go using abigen here it's my go code

package main

import ( "fmt" "math/big"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto"

)

func main() { auth, client := setup() _, _, entry, err := DeployTestContract(auth, client) if err != nil { panic(err) } client.Commit()

res, err := entry.EncodeVarint(nil, big.NewInt(10))
if err != nil {
    panic(err)
}
fmt.Println("EncodeVarint", res)
res, err = entry.EncodeVarintAssemblyNomstore8(nil, big.NewInt(10))
if err != nil {
    panic(err)
}
fmt.Println("EncodeVarintAssemblyNomstore8", res)
res, err = entry.EncodeVarintAssembly(nil, big.NewInt(10))
if err != nil {
    panic(err)
}
fmt.Println("EncodeVarintAssembly", res)

}

func setup() (bind.TransactOpts, backends.SimulatedBackend) { gAlloc := make(map[common.Address]core.GenesisAccount, 1) deployerKey, err := crypto.GenerateKey() if err != nil { panic(err) } deployerAuth := bind.NewKeyedTransactor(deployerKey) gAlloc[deployerAuth.From] = core.GenesisAccount{Balance: big.NewInt(1000000000000000000)} // 1 eth client := backends.NewSimulatedBackend(gAlloc, 1000000000000000000) // 1 eth return deployerAuth, client }

versions are

$ solc --version
solc, the solidity compiler commandline interface
Version: 0.8.9+commit.e5eed63a.Linux.g++
$ abigen --version
abigen version 1.10.0-stable

a snippet of my go.mod

module experiments/solidity_varint
go 1.17

require github.com/ethereum/go-ethereum v1.10.0 ...

output of running the main is:

EncodeVarint [10]
EncodeVarintAssemblyNomstore8 [0]
panic: out of gas

goroutine 1 [running]: main.main() /home/giulio/go/src/experiments/solidty_varint/main.go:34 +0x24b exit status 2

Apparently the mstore8 instruction makes the whole call goes out of gas. Is there any reason?? Where is the error in my mstore8 assembly call?

Note: This varint functions are decoding function for a protobuf solidity runtime decoder I am working with/on

user84917
  • 11
  • 1

1 Answers1

1

You are not really running out of gas your are seeing an EVM panic.

I think it is coming from overwriting the arrays lengths (see this answer).

In your implementation :

  function encode_varint_assembly(uint256 x, uint256 p, bytes memory bs) internal pure returns (uint256) {
        uint256 sz = 0;
        assembly {
            let bsptr := add(bs, p)
            let byt := and(x, 0x7f)
            for {} gt(shr(7, x), 0) {} {
                mstore8(bsptr, or(0x80, byt))
                bsptr := add(bsptr, 1)
                sz := add(sz, 1)
                x := shr(7, x)
                byt := and(x, 0x7f)
            }
            mstore8(bsptr, byt)
            sz := add(sz, 1)
        }
        return sz;
    }

I think that this line is the problem :

let bsptr := add(bs,p)

As you call your functions with p = 0 from your TestContract you are effectively writing : let bsptr := add(bs, 0) but bs being an array, bsptr point to the array length. Rewriting it will send the EVM into panic.

Changing it to :

let bsptr := add(bs,32)

makes bsptr points to the actual data of the array. (skipping the first 32bytes reserved for the read only array size)

With this change calls to encode_varint and encore_varint_assembly are both successfull and return the same value. I don't know much about protobuf so I cannot really validate the content of the returned value...

If I understand correctly, p might be an offset in the array where you'd want to write your encoded integer x. If so, this might be more correct regarding what you want to do :

let bsptr := add(bs, add(32, p))
hroussille
  • 7,661
  • 2
  • 6
  • 29