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

1 Answer 1

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)) 

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.