spacebudz / lucid

Lucid is a library, which allows you to create Cardano transactions and off-chain code for your Plutus contracts in JavaScript, Deno and Node.js.
https://lucid.spacebudz.io
MIT License
340 stars 139 forks source link

Error with Data.Enum #172

Closed ignaciodopazo closed 1 year ago

ignaciodopazo commented 1 year ago

I want to produce a Enum that maps to a sum type in Haskell, but Lucid crashes while building the enum in some cases. It follows the simplest example that I could make it fail with

const simpleEnum = Data.Enum([Data.Bytes()]);

which happens with Data.Integer(), Data.Any() as well. The error in all cases is the following

TypeError: Cannot read properties of undefined (reading '0')
          item.anyOf[0].fields.length === 0
                    ^
    at path/to/lucid/src/plutus/data.ts:148:21

I tried modifying this test (changing Data.Literal("Left") with Data.Bytes()) and it explodes as well.

My particular use case was to represent the Credential Plutus type, and their constructors PubKeyCredential and ScriptCredential are PubKeyHash and ScriptHash which both are newtypes wrapping PlutusTx.BuiltinByteString that end up being stripped down to just bytes in Plutus Data format.

So, the following code

const PubKeyCredential = Data.Bytes();

const ScriptCredential = Data.Bytes();

const Credential = Data.Enum([
  PubKeyCredential,
  ScriptCredential,
]);

does not work, while I believe it should.

My workaround was building a ScriptCredential by hand using Constr

const credential = new Constr(
  1,
  ["791fbf5d91b413963de25662de5ddfab29a777c03ac4d4adbb3b9a71"],
)
alessandrokonrad commented 1 year ago

Enum is a bit more difficult and also not straightforward I have to admit. I can probably improve this over time. But Credential would look like this:

const Credential = Data.Enum([
  Data.Object({
    PublicKeyCredential: Data.Tuple([
      Data.Bytes(),
    ]),
  }),
  Data.Object({
    ScriptCredential: Data.Tuple([
      Data.Bytes(),
    ]),
  }),
]);

The rules for enums are the following. If the enum item doesn't have args then you use Data.Literal(). If it does have args then you use a combination of Data.Object() and Data.Tuple. This is the only way to represent those enums properly in JavaScript. For example:

const MyEnum = Data.Enum([
  Data.Literal("Buy"),
  Data.Object({ Sell: Data.Tuple([Data.Bytes()]) }),
]);
ignaciodopazo commented 1 year ago

Fair enough, it works perfectly! Thanks for the explanation.