TVM-Solidity-Compiler
TVM-Solidity-Compiler copied to clipboard
TvmSlice.decode can't decode large structures
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();
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);
}
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.
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=)).
<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).