aiken-lang / aiken

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

Invalid code successfully typechecks #917

Closed fallen-icarus closed 2 months ago

fallen-icarus commented 2 months ago

What Git revision are you using?

aiken v1.0.26-alpha+unknown

Describe what the problem is?

The following code typechecks, but it shouldn't.

use aiken/dict.{Dict}
use aiken/list
use aiken/transaction/value.{Value, PolicyId, AssetName}

pub fn extract_offer_quantity(val: Value) -> (Int,Int,Bool) {
  let foo = fn(_x: (PolicyId,Dict<AssetName,Int>), acc: (Int,Int,Bool)) {
    let (offer_quantity, ada_quantity, has_proper_beacons) = acc

    (offer_quantity, ada_quantity) // This is wrong. It is missing the Bool.
  }

  list.foldl(
    value.to_dict(val) |> dict.to_list(_), 
    (0,0,False), 
    foo
  )
}

The inner foo function is supposed to return (Int,Int,Bool) when used with list.foldl, but in the above code, it returns (Int,Int). aiken check just gives a warning that has_proper_beacons is unused. If I change it to return (offer_quantity, ada_quantity, has_proper_beacons), aiken check successfully typechecks again, but this time without the warning.

What should be the expected behavior?

The above code could should not typecheck.

KtorZ commented 2 months ago

Oof... That's bad. Seems like this broke somewhere between v1.0.24 and v1.0.25. I'll do a quick git bisect to identify the issue. And seems like we have a hole in our test coverage.

Thanks for reporting @fallen-icarus.

KtorZ commented 2 months ago

The regression has been introduced in: ed9f5c6ef73b86b128adf81b8e1babe6ee1a3821

Probably in the PartialEq instance:

                    name == name2
                        && module == module2
                        && public == public2
                        && args.iter().zip(args2).all(|(left, right)| left == right)

The zip here is likely throwing all exceeding arguments between two sets. So it's only validating arguments up to the shortest length. In your example, there's (Int, Int) being checked against (Int, Int, Bool), so it passes 🤦.