Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use serialised lambdas to reduce main contract size #73

Closed
purcell opened this issue Apr 12, 2021 · 1 comment
Closed

Use serialised lambdas to reduce main contract size #73

purcell opened this issue Apr 12, 2021 · 1 comment

Comments

@purcell
Copy link
Contributor

purcell commented Apr 12, 2021

Background

Our contract has a very large and complex storage type, and a large number of entrypoints, with a variety of parameter types. When built monolithically, the resulting compiled contract size is prohibitively large, ~60kb. As a result, we have been exploring storing entrypoint code as serialised lambdas in a big_map, borrowing techniques from Dani's excellent article on the topic.

Naïve serialisation of endpoint functions

Our starting point was deserialising each lazy endpoint function into its exact differentiated function type in the contract's main (i.e. state -> specific_params -> (ops, state)), but this leads to lots of local duplication of the storage and parameter types, such that the main contract size ballooned to 97kb: the entrypoint code had been removed from the main contract, but additional type costs dwarf the cost of the code.

Monomorphisation of serialized functions

Presumably to address this issue, Dani's technique involves monomorphising the deserialised endpoint functions by making them all take their parameters in Bytes-serialised form (i.e. state -> bytes -> (ops, state)). This does not work directly for our use case because some of our endpoint parameters involve tickets, which are not serializable.

We have instead tried an approach of monomorphisation via a sum type which combines the possible params of all the entrypoints: the serialised entrypoints then accept the large sum type and internally match partially on their corresponding constructor (i.e. state -> all_possible_params -> (ops, state)). In this approach, the act of typechecking the unpacked function then becomes spectacularly expensive, exceeding all reasonable gas limits: both the large storage and large parameter sum type appear in the signature of the serialised function, and unification of this with the call site appears to make Michelson explode.

This approach is therefore not viable, but it has yielded a code generation facility that makes it easier to experiment with the packing scheme.

Proposed approach

Our next approach is to read (and thereby consume) all incoming tickets in the main contract, so that the endpoint implementation functions themselves take parameters that are entirely serializable, then we can wrap them into lazy serialized functions and apply Dani's monomorphisation technique. This should allow us to avoid referencing any large parameter sum type in individual lazy functions.

@purcell
Copy link
Contributor Author

purcell commented Apr 13, 2021

This is now done: the core contract weighs in at 10kb, and the endpoints are packed as functions state -> bytes -> (ops, state). Tickets supplied by users are read and verified at the boundary inside the core contract, and their contents are included in the payloads serialised as bytes.

Example cost of calling touch (with an empty checker):

    Fee to the baker: ꜩ0.045499
    Expected counter: 8
    Gas limit: 452271

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant