TVM-Solidity-Compiler icon indicating copy to clipboard operation
TVM-Solidity-Compiler copied to clipboard

TvmSlice.decode can't decode large structures

Open medved239 opened this issue 3 years ago • 4 comments

tvm.encodeBody works fine with large structures which don't fit into one cell, but slice.decodeFunctionParams gets cell underflow exception when trying to decode such body. So this case breaks the invariant that I can encode some message payload with tvm.encodeBody(), then decode it with TvmSlice.decodeFunctionParams() and get the same result.

Here is some sample code.

pragma ton-solidity ^0.44.0;

contract Mem {
    constructor() public {
        tvm.accept();
    }

    struct BigStruct {
      uint256 a;
      uint256 b;
      uint256 c;
      uint256 d;
    }

    BigStruct really_big;

    function set(BigStruct me_also_big) public {}

    // gets cell underflow
    function get() public returns (BigStruct, BigStruct) {
        TvmCell hm = tvm.encodeBody(Mem.set, really_big);
        TvmSlice slice = hm.toSlice();
        BigStruct me_also_big = slice.decodeFunctionParams(Mem.set);
        return (really_big, me_also_big);
    }
}

The same issue also holds for TvmBuilder.store() and TvmSlice.decode();

medved239 avatar May 26 '21 18:05 medved239

tvm.encodeBody encodes the function ID + the parameters. slice.decodeFunctionParams decodes only the parameters. Correct code:

	function get() public returns (BigStruct, BigStruct) {
		TvmCell hm = tvm.encodeBody(Mem.set, really_big);
		TvmSlice slice = hm.toSlice();
		uint32 funId = slice.decode(uint32);
		require(funId == tvm.functionId(Mem.set));
		BigStruct me_also_big = slice.decodeFunctionParams(Mem.set);
		return (really_big, me_also_big);
	}

IgorKoval avatar May 27 '21 21:05 IgorKoval

Oh, well, my mistake. Initially I faced that TvmSlice.decode doesn't decode large structures, and thought it is also the case for decodeFunctionParams, but didn't test it properly.

So it's all right with decodeFunctionParams, but this code still gets cell underflow

function get() public returns (BigStruct, BigStruct) {
    TvmCell hm = tvm.encodeBody(Mem.set, really_big);
    TvmSlice slice = hm.toSlice();
    uint32 funId = slice.decode(uint32);
    require(funId == tvm.functionId(Mem.set));
    BigStruct me_also_big = slice.decode(BigStruct);
    return (really_big, me_also_big);
}

and works fine when commenting one of the BigStructure fields at its declaration. I think it is strange that you can't decode large structures with TvmSlice.decode, but can do it with TvmSlice.decodeFunctionParams.

medved239 avatar May 27 '21 23:05 medved239

We have updated docs. https://github.com/tonlabs/TON-Solidity-Compiler/blob/master/API.md#tvmbuilderstore Make sure the entire struct fits into the builder. <Slice>.decode() works only with current slice and don't jump into reference. Because slice may have at most 4 refs, and in which ref we should jump to decode rest fields in struct. decodeFunctionParams function knows that parameters are stored in chain of cells (like a train=)).

IgorKoval avatar May 31 '21 19:05 IgorKoval

<Slice>.decode() works only with current slice and don't jump into reference.

But it's not the case for optional type decoding, is it? When optional has some value, this value is stored in a separate cell; the current slice contains only a single 1. And you have already implemented optional type decoding (thanks for that btw). Mappings also are decodable, and for decoding you also need to jump to some ref.

Because slice may have at most 4 refs, and in which ref we should jump to decode rest fields in struct.

It seems natural to me jumping to the first (unloaded) ref, as TVM primitives for slice decoding do. After the first jump you know that all the subsequent cells in struct serialization also form a train.

Maybe you mean that we don't know when we have to jump to another cell: but since it's a good property that any given type always serializes in the same way, it seems natural to store (and decode) large structures as if the cell we use contained only the structure we are encoding/decoding. If a programmer wants to store the structure along with some other data, it is her responsibility to be sure that structure fits into builder (taking into account the remaining number of refs).

medved239 avatar May 31 '21 23:05 medved239