aiken-lang / aiken

A modern smart contract platform for Cardano
https://aiken-lang.org
Apache License 2.0
470 stars 91 forks source link

Improve developer experience around parameterized validators #937

Closed KtorZ closed 2 months ago

KtorZ commented 5 months ago

Motivation

Context

Parameterized validators are a thing, and people use them constantly. The most common use-case we see is usually a mint validator coupled with a spend validator, using some tokens minted in the former to track state and ensure a certain execution flow.

We do currently provide support for parameterized validator in two steps:

  1. The language provides a syntax for declaring parameterized validators:

    validator foo(param1: Type1, param2: Type2) {
    
    }
  2. The command-line provides a way to apply an argument to a parameterized validator, one-by-one.

The effect of (2) generates a new blueprint that can be stored next to the original.

Problem(s)

Threading parameters throughout

Because parameters are effectively captured as function arguments, they need to be passed around in all functions that needs them. They are semantically treated as "constants", but can't be globally referenced like constants. This leads to a frustrating user experience which has been noted a couple of times already:

Cumbersome interface

As noted in https://github.com/aiken-lang/aiken/issues/927, the command-line interface can feel quite unpractical, especially in the case of multiple parameters. Calling the same command multiple times feel redundant since, when applying one parameters, others are usually also known.

Hard to manage multiple environments

Another annoyance with the interface regards how we re-generate a full blueprint file. Tracking multiple versions across multiple environments (preprod, preview, mainnet, ...) can rapidly become cumbersome. There's no guidelines on how to do that properly, and the interface is kind of working against you.

Proposed Solution

We will tackle the problem from multiple angles, hoping to provide a better UX overall while keeping the essence of what also works nicely with parameterized validators today.

Conditional module compilation

We will introduce a new set of special modules under the env top-level directory. Each of those module shall be named after an environment (which is freely defined by project owners).

Yet, only one module from this directory will be compiled and made accessible to the rest of the program under project/env. The choice of module will be driven from the command-line using a new CLI option: --env. When present, the folder env is expected to have at least one module named default.ak which will serve as default when the command-line option is omitted.

When present, the option value should match one of the module's basename (without extension). So for example, one could imagine the following structure:

env
├── default.ak
├── preprod.ak
└── preview.ak

Such that aiken build --env default would taget default.ak, aiken build --env preprod would target preprod.ak and so on. aiken build would also target default.ak.

There shall be no constraint on the environment names other than default and them being valid module identifier in Aiken. Modules are then plain Aiken modules, which are assume to export a similar API with variations relevant to the specific environment (even functions).

Static configuration in Toml

In a similar fashion, the aiken.toml file will now support an extra [config] field, with key/value pairs nested under environments. Values are only allowed to be:

For example:

[config.default]
network = "mainnet"

[config.preview]
network = "preview"

[config.preprod]
network = "preview"

(Note: we will need to agree on a syntax for strings and bytearrays, especially hex-encoded bytearrays).

In a similar fashion to environments, only one configuration will be injected in the program under project/config. The config will be conditionally selected by the --env option on build and check. In Aiken, project/config will expose identifiers with pre-constructed values matching those specified in the configuration and made globally available.

Rationale

(will complete soon)