diff --git a/package-lock.json b/package-lock.json index 7b30232d..8853d17c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "name": "@helios-lang/compiler", - "version": "0.17.0-13", + "version": "0.17.0-28", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@helios-lang/compiler", - "version": "0.17.0-13", + "version": "0.17.0-28", "license": "BSD-3-Clause", "dependencies": { "@helios-lang/codec-utils": "^0.1.32", "@helios-lang/compiler-utils": "^0.1.53", - "@helios-lang/ir": "^0.1.12", + "@helios-lang/ir": "^0.1.13", "@helios-lang/type-utils": "^0.1.20", "@helios-lang/uplc": "^0.1.35" }, @@ -53,9 +53,9 @@ } }, "node_modules/@helios-lang/ir": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@helios-lang/ir/-/ir-0.1.12.tgz", - "integrity": "sha512-SlaxgjvtqDl9HylPD5vD4nLCMNXxG8EuLVm+j1DhgJnSDBu4wScFptXjMQxEgrR/Ez04zie8je9NzVCBFQ/xwg==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@helios-lang/ir/-/ir-0.1.13.tgz", + "integrity": "sha512-G09wq/u4a8TqqFNFiFvSYRhaUzt2uq1rfQxnOSnPbNtfaME6MU37ewut++J8r82+rlsAHf1ZbJCY2PSOSkSaNg==", "dependencies": { "@helios-lang/codec-utils": "^0.1.32", "@helios-lang/compiler-utils": "^0.1.53", @@ -157,9 +157,9 @@ } }, "@helios-lang/ir": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@helios-lang/ir/-/ir-0.1.12.tgz", - "integrity": "sha512-SlaxgjvtqDl9HylPD5vD4nLCMNXxG8EuLVm+j1DhgJnSDBu4wScFptXjMQxEgrR/Ez04zie8je9NzVCBFQ/xwg==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@helios-lang/ir/-/ir-0.1.13.tgz", + "integrity": "sha512-G09wq/u4a8TqqFNFiFvSYRhaUzt2uq1rfQxnOSnPbNtfaME6MU37ewut++J8r82+rlsAHf1ZbJCY2PSOSkSaNg==", "requires": { "@helios-lang/codec-utils": "^0.1.32", "@helios-lang/compiler-utils": "^0.1.53", diff --git a/package.json b/package.json index bfc90fb9..d5f5db8b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@helios-lang/compiler", - "version": "0.17.0-28", + "version": "0.17.0-29", "description": "Helios is a Domain Specific Language that compiles to Plutus-Core (i.e. Cardano on-chain validator scripts). Helios is a non-Haskell alternative to Plutus. With this library you can compile Helios scripts and build Cardano transactions, all you need to build 100% client-side dApps for Cardano.", "main": "src/index.js", "types": "types/index.d.ts", @@ -53,7 +53,7 @@ "dependencies": { "@helios-lang/codec-utils": "^0.1.32", "@helios-lang/compiler-utils": "^0.1.53", - "@helios-lang/ir": "^0.1.12", + "@helios-lang/ir": "^0.1.13", "@helios-lang/type-utils": "^0.1.20", "@helios-lang/uplc": "^0.1.35" } diff --git a/src/codegen/makeRawFuncs.js b/src/codegen/makeRawFuncs.js index b7e3a75d..1f3d387d 100644 --- a/src/codegen/makeRawFuncs.js +++ b/src/codegen/makeRawFuncs.js @@ -9506,6 +9506,14 @@ export function makeRawFunctions(simplify, isTestnet) { }` ) ) + add( + new RawFunc( + "__helios__assetclass__mph_data", + `(self) -> { + __helios__common__enum_field_0(self) + }` + ) + ) add( new RawFunc( "__helios__assetclass__token_name", @@ -9514,6 +9522,14 @@ export function makeRawFunctions(simplify, isTestnet) { }` ) ) + add( + new RawFunc( + "__helios__assetclass__token_name_data", + `(self) -> { + __helios__common__enum_field_1(self) + }` + ) + ) add( new RawFunc( "__helios__assetclass__show", @@ -10672,6 +10688,192 @@ export function makeRawFunctions(simplify, isTestnet) { }` ) ) + add( + new RawFunc( + `__helios__value__from_flat`, + `(flat_map) -> { + __core__chooseList( + flat_map, + () -> { + __core__mkNilPairData(()) + }, + () -> { + sorted_map = __helios__map[__helios__assetclass@__helios__int]__sort(flat_map)((keya, _, keyb, _) -> { + __helios__assetclass____lt(keya, keyb) + }); + + recurse_outer = (flat_map, this_mph_data, this_token_name_data, this_qty_data) -> { + __core__chooseList( + flat_map, + () -> { + __core__mkCons( + __core__mkPairData( + this_mph_data, + __core__mapData( + __core__mkCons( + __core__mkPairData( + this_token_name_data, + this_qty_data + ), + __core__mkNilPairData(()) + ) + ) + ), + __core__mkNilPairData(()) + ) + }, + () -> { + head = __core__headList(flat_map); + tail = __core__tailList(flat_map); + next_assetclass = __helios__assetclass__from_data(__core__fstPair(head)); + next_mph_data = __helios__assetclass__mph_data(next_assetclass); + next_token_name_data = __helios__assetclass__token_name_data(next_assetclass); + next_qty_data = __core__sndPair(head); + + __core__ifThenElse( + __core__equalsData(this_mph_data, next_mph_data), + () -> { + __core__ifThenElse( + __core__equalsData(this_token_name_data, next_token_name_data), + () -> { + __helios__error("duplicate assetclass in flat map (outer)") + }, + () -> { + // recurse_inner keeps inner and outer map separate + recurse_inner = (flat_map, this_mph_data, this_token_name_data, this_qty_data) -> { + __core__chooseList( + flat_map, + () -> { + (callback) -> { + callback( + __core__mkCons( + __core__mkPairData( + this_token_name_data, + this_qty_data + ), + __core__mkNilPairData(()) + ), + __core__mkNilPairData(()) + ) + } + }, + () -> { + head = __core__headList(flat_map); + tail = __core__tailList(flat_map); + next_assetclass = __helios__assetclass__from_data(__core__fstPair(head)); + next_mph_data = __helios__assetclass__mph_data(next_assetclass); + next_token_name_data = __helios__assetclass__token_name_data(next_assetclass); + next_qty_data = __core__sndPair(head); + + __core__ifThenElse( + __core__equalsData(this_mph_data, next_mph_data), + () -> { + __core__ifThenElse( + __core__equalsData(this_token_name_data, next_token_name_data), + () -> { + __helios__error("duplicate assetclass in flat map (inner)") + }, + () -> { + callback_tail = recurse_inner(tail, next_mph_data, next_token_name_data, next_qty_data); + + callback_tail((inner_tail, outer_tail) -> { + (callback) -> { + callback( + __core__mkCons( + __core__mkPairData( + this_token_name_data, + this_qty_data + ), + inner_tail + ), + outer_tail + ) + } + }) + } + )() + }, + () -> { + outer_tail = recurse_outer(tail, next_mph_data, next_token_name_data, next_qty_data); + + (callback) -> { + callback( + __core__mkCons( + __core__mkPairData( + this_token_name_data, + this_qty_data + ), + __core__mkNilPairData(()) + ), + outer_tail + ) + } + } + )() + } + )() + }; + + callback = recurse_inner(tail, next_mph_data, next_token_name_data, next_qty_data); + + callback((inner_tail, outer_tail) -> { + inner = __core__mkCons( + __core__mkPairData( + this_token_name_data, + this_qty_data + ), + inner_tail + ); + + __core__mkCons( + __core__mkPairData( + this_mph_data, + __core__mapData(inner) + ), + outer_tail + ) + }) + } + )() + }, + () -> { + outer_tail = recurse_outer(__core__tailList(flat_map), next_mph_data, next_token_name_data, next_qty_data); + + __core__mkCons( + __core__mkPairData( + this_mph_data, + __core__mapData( + __core__mkCons( + __core__mkPairData( + this_token_name_data, + this_qty_data + ), + __core__mkNilPairData(()) + ) + ) + ), + outer_tail + ) + } + )() + } + )() + }; + + head = __core__headList(sorted_map); + head_assetclass = __helios__assetclass__from_data(__core__fstPair(head)); + recurse_outer( + __core__tailList(sorted_map), + __helios__assetclass__mph_data(head_assetclass), + __helios__assetclass__token_name_data(head_assetclass), + __core__sndPair(head) + ) + } + )() + + }` + ) + ) // Cip67 namespace add(new RawFunc(`__helios__cip67__fungible_token_label`, "#0014df10")) diff --git a/src/program/version.js b/src/program/version.js index ce91820c..7417b9f6 100644 --- a/src/program/version.js +++ b/src/program/version.js @@ -1 +1 @@ -export const VERSION = "0.17.0-28" +export const VERSION = "0.17.0-29" diff --git a/src/typecheck/money.js b/src/typecheck/money.js index e54d92ea..b8368f9f 100644 --- a/src/typecheck/money.js +++ b/src/typecheck/money.js @@ -107,6 +107,8 @@ export const ValueType = new GenericType({ ], self ), + from_flat: new FuncType([MapType$(AssetClassType, IntType)], self), + // TODO: should be getter lovelace: new FuncType([IntType], self), new: new FuncType([AssetClassType, IntType], self), sum: (() => { diff --git a/test/mintingpolicyhash.test.js b/test/mintingpolicyhash.test.js index 19c96f99..f4eae72f 100644 --- a/test/mintingpolicyhash.test.js +++ b/test/mintingpolicyhash.test.js @@ -1,23 +1,19 @@ import { describe, it } from "node:test" -import { True, bytes, compileAndRunMany } from "./utils.js" +import { True, bytes, compileForRun } from "./utils.js" describe("MintingPolicyHash", () => { - compileAndRunMany([ - { - description: "can be converted to ScriptHash and back", - main: ` - testing mph_to_from_script_hash - func main(mph: MintingPolicyHash) -> Bool { - sh = mph.to_script_hash(); - mph_ = MintingPolicyHash::from_script_hash(sh); - mph == mph_ - }`, - inputs: [ - bytes( - "00112233445566778899aabbccddeeff00112233445566778899aabb" - ) - ], - output: True - } - ]) + it("can be converted to ScriptHash and back", () => { + const runner = compileForRun(` + testing mph_to_from_script_hash + func main(mph: MintingPolicyHash) -> Bool { + sh = mph.to_script_hash(); + mph_ = MintingPolicyHash::from_script_hash(sh); + mph == mph_ + }`) + + runner( + [bytes("00112233445566778899aabbccddeeff00112233445566778899aabb")], + True + ) + }) }) diff --git a/test/real.test.js b/test/real.test.js index 8e1421c0..f1685833 100644 --- a/test/real.test.js +++ b/test/real.test.js @@ -1,8 +1,9 @@ -import { describe } from "node:test" +import { describe, it } from "node:test" import { False, True, compileAndRunMany, + compileForRun, int, ratio, real, @@ -95,423 +96,475 @@ describe("Real", () => { Real::max(a, b) }` - compileAndRunMany([ - { - description: "literal 0.0", - main: `testing lit_real_0 + describe("Literals", () => { + it("literal 0.0", () => { + const runner = compileForRun(`testing lit_real_0 func main() -> Real { 0.0 - }`, - inputs: [], - output: real(0) - }, - { - description: "literal 1.0", - main: `testing lit_real_1 + }`) + + runner([], real(0)) + }) + + it("literal 1.0", () => { + const runner = compileForRun(`testing lit_real_1 func main() -> Real { 1.0 - }`, - inputs: [], - output: real(1) - }, - { - description: "literal 1.0 == 1.0", - main: realEq1Script, - inputs: [real(1)], - output: True - }, - { - description: "literal 1.0 != -1.0", - main: realEq1Script, - inputs: [real(-1)], - output: False - }, - { - description: "equals self", - main: `testing real_eq_self + }`) + + runner([], real(1)) + }) + }) + + describe("Real == Real", () => { + const runner1 = compileForRun(`testing real_eq_1 + func main(a: Real) -> Bool { + a == 1.0 + }`) + + it("literal 1.0 equals 1.0", () => { + runner1([real(1)], True) + }) + + it("literal 1.0 isn't equal to -1.0", () => { + runner1([real(-1)], False) + }) + + it("equals self", () => { + const runner = compileForRun( + `testing real_eq_self func main(a: Real) -> Bool { a == a - }`, - inputs: [real(2345.142351)], - output: True - }, - { - description: "equals ok for many decimals", - main: realEqScript, - inputs: [real(123.123123), real(123.123123)], - output: True - }, - { - description: "equals nok for many decimal with one difference", - main: realEqScript, - inputs: [real(123.123123), real(123.123124)], - output: False - }, - { - description: "neq self always false", - main: `testing real_neq_self - func main(a: Real) -> Bool { - a != a - }`, - inputs: [real(0)], - output: False - }, - { - description: "neq ok for many decimals with one difference", - main: realNeqScript, - inputs: [real(123.123123), real(123.123124)], - output: True - }, - { - description: "negative 1.0 == -1.0", - main: realNegScript, - inputs: [real(1)], - output: real(-1) - }, - { - description: "negative 0.0 == 0.0", - main: realNegScript, - inputs: [real(0)], - output: real(0) - }, - { - description: "negative -1.0 == 1.0", - main: realNegScript, - inputs: [real(-1)], - output: real(1) - }, - { - description: "+ 1.0 == 1.0", - main: `testing real_pos - func main(a: Real) -> Real { - +a - }`, - inputs: [real(1)], - output: real(1) - }, - { - description: "1.0 + 0.0 == 1.0", - main: `testing real_add_0 - func main(a: Real) -> Real { - a + 0.0 - }`, - inputs: [real(1)], - output: real(1) - }, - { - description: "1.0 + 0.5 == 0.5", - main: realAddScript, - inputs: [real(1), real(0.5)], - output: real(1.5) - }, - { - description: "1.0 + 1000000000.5 == 1000000001.5", - main: realAddScript, - inputs: [real(1.0), real(1000000000.5)], - output: real(1000000001.5) - }, - { - description: "1.0 - 0.0 == 1.0", - main: `testing real_sub_0 - func main(a: Real) -> Real { - a - 0.0 - }`, - inputs: [real(1.0)], - output: real(1.0) - }, - { - description: "0.0 - 1.0 == -1.0", - main: `testing real_0_sub - func main(a: Real) -> Real { - 0.0 - a - }`, - inputs: [real(1.0)], - output: real(-1.0) - }, - { - description: "sub self == 0.0", - main: `testing real_sub_self - func main(a: Real) -> Real { - a - a - }`, - inputs: [real(123.123123)], - output: real(0.0) - }, - { - description: "10.5 - 1.5 == 9.0", - main: realSubScript, - inputs: [real(10.5), real(1.5)], - output: real(9) - }, - { - description: "1.0 * 0.0 == 0.0", - main: `testing real_mul_0 - func main(a: Real) -> Real { - a * 0.0 - }`, - inputs: [real(1.0)], - output: real(0.0) - }, - { - description: "10.123123 * 1.0 == 10.123123", - main: `testing real_mul_1 - func main(a: Real) -> Real { - a * 1.0 - }`, - inputs: [real(10.123123)], - output: real(10.123123) - }, - { - description: "2.0 * -0.5 == -1.0", - main: realMulScript, - inputs: [real(2), real(-0.5)], - output: real(-1.0) - }, - { - description: "1000000.0 * 0.000001 == 1.0", - main: realMulScript, - inputs: [real(1000000), real(0.000001)], - output: real(1.0) - }, - { - description: "0.100001 * 10 == 1.000010 ", - main: realMulScript, - inputs: [real(0.100001), real(10)], - output: real(1.00001) - }, - { - description: "-0.100001 * 10 == -1.000010", - main: realMulScript, - inputs: [real(-0.100001), real(10)], - output: real(-1.00001) - }, - { - description: "1.0 / literal 0.0 results in error", - main: `testing real_div_0 - func main(a: Real) -> Real { - a / 0.0 - }`, - inputs: [real(1.0)], - output: { error: "" } - }, - { - description: "literal 0.0 / 1.0 == 0.0", - main: `testing real_0_div - func main(a: Real) -> Real { - 0.0 / a - }`, - inputs: [real(1.0)], - output: real(0.0) - }, - { - description: "123.123123 / literal 1.0 == 123.123123", - main: `testing real_div_1 - func main(a: Real) -> Real { - a / 1.0 - }`, - inputs: [real(123.123123)], - output: real(123.123123) - }, - { - description: "123.123123 / 123.123123 == 1.0", - main: `testing real_div_self - func main(a: Real) -> Real { - a / a - }`, - inputs: [real(123.123123)], - output: real(1.0) - }, - { - description: "2.5 / 2.0 == 1.25", - main: realDivScript, - inputs: [real(2.5), real(2.0)], - output: real(1.25) - }, - { - description: "1.000010 / 10 = 0.100001", - main: realDivScript, - inputs: [real(1.00001), real(10)], - output: real(0.100001) - }, - { - description: "-1.000010 / 10 = -0.100001", - main: realDivScript, - inputs: [real(-1.00001), real(10)], - output: real(-0.100001) - }, - { - description: "1.000010.trunc() == 1", - main: realTruncScript, - inputs: [real(1.00001)], - output: int(1.0) - }, - { - description: "1.999999.trunc() == 1", - main: realTruncScript, - inputs: [real(1.999999)], - output: int(1) - }, - { - description: "-1.000010.trunc() == -1", - main: realTruncScript, - inputs: [real(-1.00001)], - output: int(-1) - }, - { - description: "-1.999999.trunc() == -1", - main: realTruncScript, - inputs: [real(-1.999999)], - output: int(-1) - }, - { - description: "1.000010.floor() == 1", - main: realFloorScript, - inputs: [real(1.00001)], - output: int(1) - }, - { - description: "1.999999.floor() == 1", - main: realFloorScript, - inputs: [real(1.999999)], - output: int(1) - }, - { - description: "-1.000010.floor() == -2", - main: realFloorScript, - inputs: [real(-1.00001)], - output: int(-2) - }, - { - description: "-1.999999.floor() == -2", - main: realFloorScript, - inputs: [real(-1.999999)], - output: int(-2) - }, - { - description: "1.000010.ceil() == 2", - main: realCeilScript, - inputs: [real(1.00001)], - output: int(2) - }, - { - description: "1.999999.ceil() == 2", - main: realCeilScript, - inputs: [real(1.999999)], - output: int(2) - }, - { - description: "-1.000010.ceil() == -1", - main: realCeilScript, - inputs: [real(-1.00001)], - output: int(-1) - }, - { - description: "-1.999999.ceil() == -1", - main: realCeilScript, - inputs: [real(-1.999999)], - output: int(-1) - }, - { - description: "Real::sqrt(2) == 1.414213", - main: realSqrtScript, - inputs: [real(2)], - output: real(1.414213) - }, - { - description: "Real::sqrt(4) == 2.0", - main: realSqrtScript, - inputs: [real(4)], - output: real(2) - }, - { - description: "Real::sqrt(0) == 0.0", - main: realSqrtScript, - inputs: [real(0)], - output: real(0) - }, - { - description: "Real::sqrt(-1) throws an error", - main: realSqrtScript, - inputs: [real(-1)], - output: { error: "" } - }, - { - description: "Real::sqrt(8) == 2.828427", - main: realSqrtScript, - inputs: [real(8)], - output: real(2.828427) - }, - { - description: "Real::sqrt(1024) == 32", - main: realSqrtScript, - inputs: [real(1024)], - output: real(32) - }, - { - description: "Real::sqrt(1000000) == 1000", - main: realSqrtScript, - inputs: [real(1000000)], - output: real(1000) - }, - { - description: "Real::sqrt(1000000) == 1000", - main: realSqrtScript, - inputs: [real(1000000)], - output: real(1000) - }, - { - description: "Real::sqrt(1_000_000_000) == 31622.776601", - main: realSqrtScript, - inputs: [real(1_000_000_000)], - output: real(31622.776601) - }, - { - description: "Real::from_data(iData 1_000_000) == 1.0", - main: realFromDataScript, - inputs: [int(1_000_000)], - output: real(1.0) - }, - { - description: '0.020176.show() == "0.020176"', - main: realShowScript, - inputs: [real(0.020176)], - output: str("0.020176") - }, - { - description: '-0.020176.show() == "-0.020176"', - main: realShowScript, - inputs: [real(-0.020176)], - output: str("-0.020176") - }, - { - description: '-305948.394872.show() == "-305948.394872"', - main: realShowScript, - inputs: [real(-305948.394872)], - output: str("-305948.394872") - }, - { - description: '-0.394872.show() == "-0.394872"', - main: realShowScript, - inputs: [real(-0.394872)], - output: str("-0.394872") - }, - { - description: "2.5.to_ratio() == 2_500_000 / 1_000_000", - main: realToRatioScript, - inputs: [real(2.5)], - output: ratio(2_500_000, 1_000_000) - }, - { - description: "Real::min(-1.0, -1.1) == -1.1", - main: realMinScript, - inputs: [real(-1.0), real(-1.1)], - output: real(-1.1) - }, - { - description: "Real::max(-1.0, -1.1) == -1.0", - main: realMaxScript, - inputs: [real(-1.0), real(-1.1)], - output: real(-1.0) - } - ]) + }` + ) + + runner([real(2345.142351)], True) + }) + + const runner2 = compileForRun(`testing real_eq + func main(a: Real, b: Real) -> Bool { + a == b + }`) + + it("ok for many decimals", () => { + runner2([real(123.123123), real(123.123123)], True) + }) + + it("nok for many decimal with one difference", () => { + runner2([real(123.123123), real(123.123124)], False) + }) + }) + + describe("Real != Real", () => { + const runner1 = compileForRun(`testing real_neq_self + func main(a: Real) -> Bool { + a != a + }`) + + it("neq self always false", () => { + runner1([real(0)], False) + }) + + const runner2 = compileForRun(`testing real_neq + func main(a: Real, b: Real) -> Bool { + a != b + }`) + + it("neq ok for many decimals with one difference", () => { + runner2([real(123.123123), real(123.123124)], True) + }) + }) + + describe("- Real", () => { + const runner = compileForRun(`testing real_neg + func main(a: Real) -> Real { + -a + }`) + + it("negative 1.0 == -1.0", () => { + runner([real(1)], real(-1)) + }) + + it("negative 0.0 == 0.0", () => { + runner([real(0)], real(0)) + }) + + it("negative -1.0 == 1.0", () => { + runner([real(-1)], real(1)) + }) + }) + + describe("+ Real", () => { + const runner = compileForRun(`testing real_pos + func main(a: Real) -> Real { + +a + }`) + + it("+ 1.0 == 1.0", () => { + runner([real(1)], real(1)) + }) + }) + + describe("Real + Real", () => { + const runner1 = compileForRun(`testing real_add_0 + func main(a: Real) -> Real { + a + 0.0 + }`) + + it("1.0 + 0.0 == 1.0", () => { + runner1([real(1)], real(1)) + }) + + const runner2 = compileForRun(`testing real_add + func main(a: Real, b: Real) -> Real { + a + b + }`) + + it("1.0 + 0.5 == 0.5", () => { + runner2([real(1), real(0.5)], real(1.5)) + }) + + it("1.0 + 1000000000.5 == 1000000001.5", () => { + runner2([real(1.0), real(1000000000.5)], real(1000000001.5)) + }) + }) + + describe("Real - Real", () => { + const runner1 = compileForRun(`testing real_sub_0 + func main(a: Real) -> Real { + a - 0.0 + }`) + + it("1.0 - 0.0 == 1.0", () => { + runner1([real(1.0)], real(1.0)) + }) + + const runner2 = compileForRun(`testing real_0_sub + func main(a: Real) -> Real { + 0.0 - a + }`) + + it("0.0 - 1.0 == -1.0", () => { + runner2([real(1.0)], real(-1.0)) + }) + + const runner3 = compileForRun(`testing real_sub_self + func main(a: Real) -> Real { + a - a + }`) + + it("sub self == 0.0", () => { + runner3([real(123.123123)], real(0.0)) + }) + + const runner4 = compileForRun(`testing real_sub + func main(a: Real, b: Real) -> Real { + a - b + }`) + + it("10.5 - 1.5 == 9.0", () => { + runner4([real(10.5), real(1.5)], real(9)) + }) + }) + + describe("Real * Real", () => { + const runner1 = compileForRun(`testing real_mul_0 + func main(a: Real) -> Real { + a * 0.0 + }`) + + it("1.0 * 0.0 == 0.0", () => { + runner1([real(1.0)], real(0.0)) + }) + + const runner2 = compileForRun(`testing real_mul_1 + func main(a: Real) -> Real { + a * 1.0 + }`) + + it("10.123123 * 1.0 == 10.123123", () => { + runner2([real(10.123123)], real(10.123123)) + }) + + const runner3 = compileForRun(`testing real_mul + func main(a: Real, b: Real) -> Real { + a * b + }`) + + it("2.0 * -0.5 == -1.0", () => { + runner3([real(2), real(-0.5)], real(-1.0)) + }) + + it("1000000.0 * 0.000001 == 1.0", () => { + runner3([real(1000000), real(0.000001)], real(1.0)) + }) + + it("0.100001 * 10 == 1.000010 ", () => { + runner3([real(0.100001), real(10)], real(1.00001)) + }) + + it("-0.100001 * 10 == -1.000010", () => { + runner3([real(-0.100001), real(10)], real(-1.00001)) + }) + }) + + describe("Real / Real", () => { + const runner1 = compileForRun(`testing real_div_0 + func main(a: Real) -> Real { + a / 0.0 + }`) + + it("1.0 / literal 0.0 results in error", () => { + runner1([real(1.0)], { error: "" }) + }) + + const runner2 = compileForRun(`testing real_0_div + func main(a: Real) -> Real { + 0.0 / a + }`) + + it("literal 0.0 / 1.0 == 0.0", () => { + runner2([real(1.0)], real(0.0)) + }) + + const runner3 = compileForRun(`testing real_div_1 + func main(a: Real) -> Real { + a / 1.0 + }`) + + it("123.123123 / literal 1.0 == 123.123123", () => { + runner3([real(123.123123)], real(123.123123)) + }) + + const runner4 = compileForRun(`testing real_div_self + func main(a: Real) -> Real { + a / a + }`) + + it("123.123123 / 123.123123 == 1.0", () => { + runner4([real(123.123123)], real(1.0)) + }) + + const runner5 = compileForRun(`testing real_div + func main(a: Real, b: Real) -> Real { + a / b + }`) + + it("2.5 / 2.0 == 1.25", () => { + runner5([real(2.5), real(2.0)], real(1.25)) + }) + + it("1.000010 / 10 = 0.100001", () => { + runner5([real(1.00001), real(10)], real(0.100001)) + }) + + it("1.000010 / 100 = 0.010000", () => { + runner5([real(1.00001), real(100)], real(0.01)) + }) + + it("-1.000010 / 10 = -0.100001", () => { + runner5([real(-1.00001), real(10)], real(-0.100001)) + }) + }) + + describe("Real.trunc", () => { + const runner = compileForRun(`testing real_trunc + func main(a: Real) -> Int { + a.trunc() + }`) + + it("1.000010.trunc() == 1", () => { + runner([real(1.00001)], int(1.0)) + }) + + it("1.999999.trunc() == 1", () => { + runner([real(1.999999)], int(1)) + }) + + it("2.trunc() == 2", () => { + runner([real(2)], int(2)) + }) + + it("-1.000010.trunc() == -1", () => { + runner([real(-1.00001)], int(-1)) + }) + + it("-1.999999.trunc() == -1", () => { + runner([real(-1.999999)], int(-1)) + }) + + it("-2.trunc() == -2", () => { + runner([real(-2)], int(-2)) + }) + }) + + describe("Real.floor", () => { + const runner = compileForRun( + `testing real_floor + func main(a: Real) -> Int { + a.floor() + }` + ) + + it("1.000010.floor() == 1", () => { + runner([real(1.00001)], int(1)) + }) + + it("1.999999.floor() == 1", () => { + runner([real(1.999999)], int(1)) + }) + + it("2.floor() == 2", () => { + runner([real(2)], int(2)) + }) + + it("-1.000010.floor() == -2", () => { + runner([real(-1.00001)], int(-2)) + }) + + it("-1.999999.floor() == -2", () => { + runner([real(-1.999999)], int(-2)) + }) + + it("-2.floor() == -2", () => { + runner([real(-2)], int(-2)) + }) + }) + + describe("Real.ceil", () => { + const runner = compileForRun(`testing real_ceil + func main(a: Real) -> Int { + a.ceil() + }`) + + it("1.000010.ceil() == 2", () => { + runner([real(1.00001)], int(2)) + }) + + it("1.999999.ceil() == 2", () => { + runner([real(1.999999)], int(2)) + }) + + it("2.ceil() == 2", () => { + runner([real(2)], int(2)) + }) + + it("-1.000010.ceil() == -1", () => { + runner([real(-1.00001)], int(-1)) + }) + + it("-1.999999.ceil() == -1", () => { + runner([real(-1.999999)], int(-1)) + }) + + it("-2.ceil() == -2", () => { + runner([real(-2)], int(-2)) + }) + }) + + describe("Real::sqrt", () => { + const runner = compileForRun(`testing real_sqrt + func main(a: Real) -> Real { + Real::sqrt(a) + }`) + + it("sqrt(0) == 0.0", () => { + runner([real(0)], real(0)) + }) + + it("sqrt(2) == 1.414213", () => { + runner([real(2)], real(1.414213)) + }) + + it("sqrt(4) == 2.0", () => { + runner([real(4)], real(2)) + }) + + it("sqrt(-1) fails", () => { + runner([real(-1)], { error: "" }) + }) + + it("sqrt(8) == 2.828427", () => { + runner([real(8)], real(2.828427)) + }) + + it("sqrt(1024) == 32", () => { + runner([real(1024)], real(32)) + }) + + it("sqrt(1000000) == 1000", () => { + runner([real(1000000)], real(1000)) + }) + + it("sqrt(1_000_000_000) == 31622.776601", () => { + runner([real(1_000_000_000)], real(31622.776601)) + }) + }) + + describe("Real::from_data", () => { + const runner = compileForRun(`testing real_from_data + func main(a: Data) -> Real { + Real::from_data(a) + }`) + + it("from_data(iData 1_000_000) == 1.0", () => { + runner([int(1_000_000)], real(1.0)) + }) + }) + + describe("Real.show", () => { + const runner = compileForRun(`testing real_show + func main(a: Real) -> String { + a.show() + }`) + + it('0.020176.show() == "0.020176"', () => { + runner([real(0.020176)], str("0.020176")) + }) + + it('-0.020176.show() == "-0.020176"', () => { + runner([real(-0.020176)], str("-0.020176")) + }) + + it('-305948.394872.show() == "-305948.394872"', () => { + runner([real(-305948.394872)], str("-305948.394872")) + }) + + it('-0.394872.show() == "-0.394872"', () => { + runner([real(-0.394872)], str("-0.394872")) + }) + }) + + describe("Real.to_ratio", () => { + const runner = compileForRun(`testing real_to_ratio + func main(a: Real) -> Ratio { + a.to_ratio() + }`) + + it("2.5.to_ratio() == 2_500_000 / 1_000_000", () => { + runner([real(2.5)], ratio(2_500_000, 1_000_000)) + }) + }) + + describe("Real::min", () => { + const runner = compileForRun(`testing real_min + func main(a: Real, b: Real) -> Real { + Real::min(a, b) + }`) + + it("min(-1.0, -1.1) == -1.1", () => { + runner([real(-1.0), real(-1.1)], real(-1.1)) + }) + }) + + describe("Real::max", () => { + const runner = compileForRun(`testing real_max + func main(a: Real, b: Real) -> Real { + Real::max(a, b) + }`) + + it("max(-1.0, -1.1) == -1.0", () => { + runner([real(-1.0), real(-1.1)], real(-1.0)) + }) + }) }) diff --git a/test/stakingcredential.test.js b/test/stakingcredential.test.js index e73ff4f1..2d80b3b9 100644 --- a/test/stakingcredential.test.js +++ b/test/stakingcredential.test.js @@ -1,25 +1,22 @@ import { describe, it } from "node:test" -import { True, bytes, compileAndRunMany } from "./utils.js" +import { True, bytes, compileForRun } from "./utils.js" describe("StakingCredential", () => { - compileAndRunMany([ - { - description: "can destructure hash from StakingCredential", - main: `testing staking_cred_destruct - func main(pkh: PubKeyHash) -> Bool { - h_ = StakingHash::new_stakekey(pkh); - cred = StakingCredential::new_hash(h_); + it("can destructure hash from StakingCredential", () => { + const runner = compileForRun(`testing staking_cred_destruct + func main(pkh: PubKeyHash) -> Bool { + h_ = StakingHash::new_stakekey(pkh); + cred = StakingCredential::new_hash(h_); - cred.switch{ - Hash{h} => h.switch{ - StakeKey{p} => p == pkh, - else => error("unexpected") - }, + cred.switch{ + Hash{h} => h.switch{ + StakeKey{p} => p == pkh, else => error("unexpected") - } - }`, - inputs: [bytes("abcd")], - output: True - } - ]) + }, + else => error("unexpected") + } + }`) + + runner([bytes("abcd")], True) + }) }) diff --git a/test/utils.js b/test/utils.js index 3252addc..aca875fd 100644 --- a/test/utils.js +++ b/test/utils.js @@ -1,5 +1,5 @@ import assert, { strictEqual, throws } from "node:assert" -import { describe, it } from "node:test" +import { it } from "node:test" import { bytesToHex, encodeUtf8 } from "@helios-lang/codec-utils" import { isLeft, isRight } from "@helios-lang/type-utils" import { @@ -105,6 +105,62 @@ export function compileAndRun(test) { }) } +/** + * @param {string} mainSrc + * @param {string[]} moduleSrcs + * @returns {(inputs: UplcData[], output: HeliosTestOutput) => void} + */ +export function compileForRun(mainSrc, moduleSrcs = []) { + const program = new Program(mainSrc, { + moduleSources: moduleSrcs, + isTestnet: true + }) + + const uplc0 = program.compile(false) + const hash0 = bytesToHex(uplc0.hash()) + const uplc1 = program.compile(true) + const hash1 = bytesToHex(uplc1.hash()) + + return (inputs, output) => { + const args = inputs.map((d) => new UplcDataValue(d)) + + const result0 = uplc0.eval(args) + + resultEquals(result0, output) + + if (hash1 != hash0) { + const result1 = uplc1.eval(args) + + resultEquals(result1, output) + + // also make sure the costs and size are smaller + const size0 = uplc0.toCbor().length + const size1 = uplc1.toCbor().length + + assert( + size1 < size0, + `optimization didn't improve size (!(${size1} < ${size0}))` + ) + + const costMem0 = result0.cost.mem + const costMem1 = result1.cost.mem + + assert( + costMem1 < costMem0, + `optimization didn't improve mem cost (!(${costMem1.toString()} < ${costMem0.toString()}))` + ) + + const costCpu0 = result0.cost.cpu + const costCpu1 = result1.cost.cpu + + assert( + costCpu1 < costCpu0, + `optimization didn't improve cpu cost (!(${costCpu1.toString()} < ${costCpu0.toString()}))` + ) + } + } +} + /** * @param {string} src * @param {UplcData[]} dataArgs @@ -316,7 +372,3 @@ function expectedResultToString(result) { function resultEquals(actual, expected) { strictEqual(cekResultToString(actual), expectedResultToString(expected)) } - -describe("utils", () => { - it("dummy", () => {}) -}) diff --git a/test/value.test.js b/test/value.test.js index d7b6df1b..a24a5dfe 100644 --- a/test/value.test.js +++ b/test/value.test.js @@ -1,549 +1,641 @@ -import { describe } from "node:test" +import { describe, it } from "node:test" import { False, True, assetclass, bytes, - compileAndRunMany, - constr, + compileForRun, int, map } from "./utils.js" describe("Value", () => { - const intLovelaceIsZeroScript = `testing lovelace_is_zero - func main(a: Int) -> Bool { - Value::lovelace(a).is_zero() - }` - - const dummyLovelaceEqScript = `testing lovelace_dummy_eq - func main(a: Int) -> Bool { - Value::lovelace(a) == Value::lovelace(a) - }` - - const lovelaceEqScript = `testing lovelace_eq - func main(a: Int, b: Int) -> Bool { - Value::lovelace(a) == Value::lovelace(b) - }` - - const dummyLovelaceNeqScript = `testing lovelace_dummy_neq - func main(a: Int) -> Bool { - Value::lovelace(a) != Value::lovelace(a) - }` - - const lovelaceNeqScript = `testing lovelace_neq - func main(a: Int, b: Int) -> Bool { - Value::lovelace(a) != Value::lovelace(b) - }` - - const lovelaceAddZeroScript = `testing lovelace_add_zero - func main(a: Int) -> Int { - (Value::lovelace(a) + Value::ZERO).get(AssetClass::ADA) - }` - - const lovelaceAddScript = `testing lovelace_add - func main(a: Int, b: Int) -> Int { - (Value::lovelace(a) + Value::lovelace(b)).get(AssetClass::ADA) - }` - - const lovelaceSumScript = `testing lovelace_sum - func main(a: Int, b: Int) -> Int { - Value::sum([]Value{Value::lovelace(a), Value::lovelace(b)}).get(AssetClass::ADA) - }` - - const lovelaceSubZeroScript = `testing lovelace_sub_zero - func main(a: Int) -> Int { - (Value::lovelace(a) - Value::ZERO).get(AssetClass::ADA) - }` - - const zeroSubLovelaceScript = `testing zero_sub_lovelace - func main(a: Int) -> Int { - (Value::ZERO - Value::lovelace(a)).get(AssetClass::ADA) - }` - - const dummyLovelaceSubScript = `testing dummy_lovelace_sub - func main(a: Int) -> Bool { - (Value::lovelace(a) - Value::lovelace(a)).is_zero() - }` - - const lovelaceSubScript = `testing lovelace_sub - func main(a: Int, b: Int) -> Int { - (Value::lovelace(a) - Value::lovelace(b)).get(AssetClass::ADA) - }` - - const dummyLovelaceMulScript = `testing dummy_lovelace_mul - func main(a: Int) -> Int { - (Value::lovelace(a)*1).get(AssetClass::ADA) - }` - - const lovelaceMulScript = `testing lovelace_mul - func main(a: Int, b: Int) -> Int { - (Value::lovelace(a)*b).get(AssetClass::ADA) - }` - - const valueAddMulScript = `testing value_add_mul - const MY_NFT: AssetClass = AssetClass::new(MintingPolicyHash::new(#abcd), #abcd) - func main(a: Int, b: Int, c: Int) -> Int { - ((Value::lovelace(a) + Value::new(MY_NFT, b))*c).get(AssetClass::ADA) - }` - - const dummyLovelaceDivScript = `testing dummy_lovelace_div - func main(a: Int) -> Int { - (Value::lovelace(a)/1).get(AssetClass::ADA) - }` - - const lovelaceDivScript = `testing lovelace_div - func main(a: Int, b: Int) -> Int { - (Value::lovelace(a)/b).get(AssetClass::ADA) - }` - - const valueAddDivScript = `testing value_add_div - const MY_NFT: AssetClass = AssetClass::new(MintingPolicyHash::new(#abcd), #abcd) - func main(a: Int, b: Int, c: Int) -> Int { - ((Value::lovelace(a) + Value::new(MY_NFT, b))/c).get(AssetClass::ADA) - }` - - const dummyLovelaceGeqScript = `testing dummy_lovelace_geq - func main(a: Int) -> Bool { - Value::lovelace(a) >= Value::lovelace(a) - }` - - const lovelaceGeqScript = `testing lovelace_geq - func main(a: Int, b: Int) -> Bool { - Value::lovelace(a) >= Value::lovelace(b) - }` - - const lovelaceContainsScript = `testing lovelace_contains - func main(a: Int, b: Int) -> Bool { - Value::lovelace(a).contains(Value::lovelace(b)) - }` - - const valueSingletonScript = `testing value_singleton - func main(n_lovelace: Int, mph: MintingPolicyHash, name: ByteArray, qty: Int) -> Bool { - asset_class = AssetClass::new(mph, name); - value = Value::lovelace(n_lovelace) + Value::new(asset_class, qty); - - value.get_singleton_asset_class() == asset_class - }` - - const twoAssetClassesValueSingletonScript = `testing value_singleton_two_asset_classes - func main(n_lovelace: Int, mph1: MintingPolicyHash, name1: ByteArray, qty1: Int, mph2: MintingPolicyHash, name2: ByteArray, qty2: Int) -> Bool { - asset_class1 = AssetClass::new(mph1, name1); - asset_class2 = AssetClass::new(mph2, name2); - value = Value::lovelace(n_lovelace) + Value::new(asset_class1, qty1) + Value::new(asset_class2, qty2); - - value.get_singleton_asset_class() == asset_class1 - }` - - const lovelaceValueSingletonScript = `testing lovelace_value_singleton - func main(n_lovelace: Int) -> Bool { - value = Value::lovelace(n_lovelace); - - value.get_singleton_asset_class() == AssetClass::ADA - }` - - const valuableSingletonScript = `testing valuable_singleton_script + describe("Value.is_zero", () => { + const runner = compileForRun(`testing lovelace_is_zero + func main(a: Int) -> Bool { + Value::lovelace(a).is_zero() + }`) + + it("1 lovelace isn't zero", () => { + runner([int(1)], False) + }) + + it("0 lovelace is zero", () => { + runner([int(0)], True) + }) + }) + + describe("Value == Value", () => { + const runner1 = compileForRun(`testing lovelace_dummy_eq + func main(a: Int) -> Bool { + Value::lovelace(a) == Value::lovelace(a) + }`) + + it("lovelace comparison to self is always true", () => { + runner1([int(-100)], True) + }) + + const runner2 = compileForRun(`testing lovelace_eq + func main(a: Int, b: Int) -> Bool { + Value::lovelace(a) == Value::lovelace(b) + }`) + + it("1000000 lovelace is equal to 1000000 lovelace", () => { + runner2([int(1_000_000), int(1_000_000)], True) + }) + + it("1000001 lovelace isn't equal to 1000000 lovelace", () => { + runner2([int(1_000_001), int(1_000_000)], False) + }) + }) + + describe("Value != Value", () => { + const runner1 = compileForRun(`testing lovelace_dummy_neq + func main(a: Int) -> Bool { + Value::lovelace(a) != Value::lovelace(a) + }`) + + it("lovelace neq comparison with self is always false", () => { + runner1([int(0)], False) + }) + + const runner2 = compileForRun(`testing lovelace_neq + func main(a: Int, b: Int) -> Bool { + Value::lovelace(a) != Value::lovelace(b) + }`) + + it("1_000_000 lovelace neq to 1_000_000 returns false", () => { + runner2([int(1_000_000), int(1_000_000)], False) + }) + + it("1_000_001 lovelace neq to 1_000_000 returns true", () => { + runner2([int(1_000_001), int(1_000_000)], True) + }) + }) + + describe("Value + Value", () => { + const runner1 = compileForRun(`testing lovelace_add_zero + func main(a: Int) -> Int { + (Value::lovelace(a) + Value::ZERO).get(AssetClass::ADA) + }`) + + it("adding zero to 1_000_000 lovelace returns 1_000_000 lovelace", () => { + runner1([int(1_000_000)], int(1_000_000)) + }) + + const runner2 = compileForRun(`testing lovelace_add + func main(a: Int, b: Int) -> Int { + (Value::lovelace(a) + Value::lovelace(b)).get(AssetClass::ADA) + }`) + + it("adding 1 to 1_000_000 lovelace returns 1_000_001 lovelace", () => { + runner2([int(1), int(1_000_000)], int(1_000_001)) + }) + }) + + describe("Value::sum", () => { + const runner = compileForRun(`testing lovelace_sum + func main(a: Int, b: Int) -> Int { + Value::sum([]Value{Value::lovelace(a), Value::lovelace(b)}).get(AssetClass::ADA) + }`) + + it("summing 1 and 1_000_000 lovelace returns 1_000_001 lovelace", () => { + runner([int(1), int(1_000_000)], int(1_000_001)) + }) + }) + + describe("Value - Value", () => { + const runner1 = compileForRun(`testing lovelace_sub_zero + func main(a: Int) -> Int { + (Value::lovelace(a) - Value::ZERO).get(AssetClass::ADA) + }`) + + it("subtracting zero from 1_000_000 lovelace returns 1_000_000 lovelace", () => { + runner1([int(1_000_000)], int(1_000_000)) + }) + + const runner2 = compileForRun(`testing zero_sub_lovelace + func main(a: Int) -> Int { + (Value::ZERO - Value::lovelace(a)).get(AssetClass::ADA) + }`) + + it("subtracting 1_000_000 from zero returns -1_000_000", () => { + runner2([int(1_000_000)], int(-1_000_000)) + }) + + const runner3 = compileForRun(`testing dummy_lovelace_sub + func main(a: Int) -> Bool { + (Value::lovelace(a) - Value::lovelace(a)).is_zero() + }`) + + it("lovelace subtracted from self is always zero", () => { + runner3([int(-1_000_001)], True) + }) + + const runner4 = compileForRun(`testing lovelace_sub + func main(a: Int, b: Int) -> Int { + (Value::lovelace(a) - Value::lovelace(b)).get(AssetClass::ADA) + }`) + + it("subtracting 1_000_000 from 1_000_001 returns 1", () => { + runner4([int(1_000_001), int(1_000_000)], int(1)) + }) + }) + + describe("Value * Int", () => { + const runner1 = compileForRun(`testing dummy_lovelace_mul + func main(a: Int) -> Int { + (Value::lovelace(a)*1).get(AssetClass::ADA) + }`) + + it("1_000_000 lovelace multiplied by 1 returns 1_000_000", () => { + runner1([int(1_000_000)], int(1_000_000)) + }) + + const runner2 = compileForRun(`testing lovelace_mul + func main(a: Int, b: Int) -> Int { + (Value::lovelace(a)*b).get(AssetClass::ADA) + }`) + + it("1_000_000 lovelace multiplied by 3 returns 3_000_000 lovelace", () => { + runner2([int(1_000_000), int(3)], int(3_000_000)) + }) + + const runner3 = compileForRun(`testing value_add_mul + const MY_NFT: AssetClass = AssetClass::new(MintingPolicyHash::new(#abcd), #abcd) + func main(a: Int, b: Int, c: Int) -> Int { + ((Value::lovelace(a) + Value::new(MY_NFT, b))*c).get(AssetClass::ADA) + }`) + + it("1_000_000 lovelace multiplied by 3 after adding an NFT returns 3_000_000 lovelace as well", () => { + runner3([int(1_000_000), int(1), int(3)], int(3_000_000)) + }) + }) + + describe("Value / Int", () => { + const runner1 = compileForRun(`testing dummy_lovelace_div + func main(a: Int) -> Int { + (Value::lovelace(a)/1).get(AssetClass::ADA) + }`) + + it("1_000_000 lovelace divided by 1 returns 1_000_000 lovelace", () => { + runner1([int(1_000_000)], int(1_000_000)) + }) + + const runner2 = compileForRun(`testing lovelace_div + func main(a: Int, b: Int) -> Int { + (Value::lovelace(a)/b).get(AssetClass::ADA) + }`) + + it("1_000_000 lovelace divided by zero throws error", () => { + runner2([int(1_000_000), int(0)], { error: "" }) + }) + + it("1_000_000 lovelace divided by -1 returns -1_000_000 lovelace", () => { + runner2([int(1_000_000), int(-1)], int(-1_000_000)) + }) + + const runner3 = compileForRun(`testing value_add_div + const MY_NFT: AssetClass = AssetClass::new(MintingPolicyHash::new(#abcd), #abcd) + func main(a: Int, b: Int, c: Int) -> Int { + ((Value::lovelace(a) + Value::new(MY_NFT, b))/c).get(AssetClass::ADA) + }`) + + it("3_000_000 lovelace divided by -3 after adding an NFT still returns -1_000_000 lovelace", () => { + runner3([int(3_000_000), int(1), int(-3)], int(-1_000_000)) + }) + }) + + describe("Value >= Value", () => { + const runner1 = compileForRun(`testing dummy_lovelace_geq + func main(a: Int) -> Bool { + Value::lovelace(a) >= Value::lovelace(a) + }`) + + it("lovelace >= self always returns true", () => { + runner1([int(1_000_000)], True) + }) + + const runner2 = compileForRun(`testing lovelace_geq + func main(a: Int, b: Int) -> Bool { + Value::lovelace(a) >= Value::lovelace(b) + }`) + + it("0 lovelace >= 0 lovelace return true", () => { + runner2([int(0), int(0)], True) + }) + + it("-1 lovelace >= 0 lovelace return false", () => { + runner2([int(-1), int(0)], False) + }) + + it("1 lovelace >= 0 lovelace return true", () => { + runner2([int(1), int(0)], True) + }) + }) + + describe("Value.contains", () => { + const runner = compileForRun(`testing lovelace_contains + func main(a: Int, b: Int) -> Bool { + Value::lovelace(a).contains(Value::lovelace(b)) + }`) + + it("1_000_000 lovelace contains 999_999 lovelace", () => { + runner([int(1_000_000), int(999_999)], True) + }) + }) + + describe("Value.get_singleton_asset_class", () => { + const runner1 = compileForRun(`testing value_singleton + func main(value: Value) -> AssetClass { + value.get_singleton_asset_class() + }`) + + it("ok for singleton", () => { + runner1( + [ + map([ + [bytes(""), map([[bytes(""), int(1_000_000)]])], + [bytes("abcd"), map([[bytes("abcd"), int(1)]])] + ]) + ], + assetclass("abcd", "abcd") + ) + }) + + it("ok for singleton for zero lovelace", () => { + runner1( + [map([[bytes("abcd"), map([[bytes("abcd"), int(1)]])]])], + assetclass("abcd", "abcd") + ) + }) + + it("fails for lovelace only", () => { + runner1([map([[bytes(""), map([[bytes(""), int(1_000_000)]])]])], { + error: "" + }) + }) + + it("fails for non-singleton", () => { + runner1([map([[bytes("abcd"), map([[bytes("abcd"), int(2)]])]])], { + error: "" + }) + }) + + it("fails for negative qty singleton", () => { + runner1( + [ + map([ + [bytes(""), map([[bytes(""), int(1_000_000)]])], + [bytes("abcd"), map([[bytes("abcd"), int(-1)]])] + ]) + ], + { error: "" } + ) + }) + + it("fails for two different asset classes", () => { + runner1( + [ + map([ + [bytes("abcd"), map([[bytes("abcd"), int(1)]])], + [bytes("abcdef"), map([[bytes(""), int(1)]])] + ]) + ], + { error: "" } + ) + }) + + it("fails for two different token names", () => { + runner1( + [ + map([ + [bytes("abcd"), map([[bytes("abcd"), int(1)]])], + [bytes("abcd"), map([[bytes(""), int(1)]])] + ]) + ], + { error: "" } + ) + }) + + const runner2 = compileForRun(`testing valuable_singleton_script - func get_singleton_wrapper[V: Valuable](v: V) -> AssetClass { - v.value.get_singleton_asset_class() - } - - func main(lovelace: Int) -> Bool { - pub_key_hash_bytes = #01234567890123456789012345678901234567890123456789012345; - address = Address::new(SpendingCredential::new_pubkey(PubKeyHash::new(pub_key_hash_bytes)), Option[StakingCredential]::None); - asset_class = AssetClass::new(MintingPolicyHash::new(#abcd), #abcd); - value = Value::lovelace(lovelace) + Value::new(asset_class, 1); - output = TxOutput::new( - address, - value, - TxOutputDatum::new_none() - ); - - get_singleton_wrapper(output) == asset_class - }` - - const valueFlattenScript = `testing value_flatten - func main(n_lovelace: Int, mph1: MintingPolicyHash, name1: ByteArray, qty1: Int, mph2: MintingPolicyHash, name2: ByteArray, qty2: Int) -> Map[AssetClass]Int { - asset_class1 = AssetClass::new(mph1, name1); - asset_class2 = AssetClass::new(mph2, name2); - value = Value::lovelace(n_lovelace) + Value::new(asset_class1, qty1) + Value::new(asset_class2, qty2); - - value.flatten() - }` - - const valueDeletePolicyScript = `testing value_delete_policy - func main( - n_lovelace: Int, - mph1: MintingPolicyHash, - name1: ByteArray, - qty1: Int, - mph2: MintingPolicyHash, - name2: ByteArray, - qty2: Int, - mph_to_delete: MintingPolicyHash - ) -> Map[AssetClass]Int { - asset_class1 = AssetClass::new(mph1, name1); - asset_class2 = AssetClass::new(mph2, name2); - value = Value::lovelace(n_lovelace) + Value::new(asset_class1, qty1) + Value::new(asset_class2, qty2); - - value.delete_policy(mph_to_delete).flatten() - }` - - const valueDeleteLovelaceScript = `testing value_delete_lovelace - func main( - n_lovelace: Int, - mph1: MintingPolicyHash, - name1: ByteArray, - qty1: Int, - mph2: MintingPolicyHash, - name2: ByteArray, - qty2: Int - ) -> Map[AssetClass]Int { - asset_class1 = AssetClass::new(mph1, name1); - asset_class2 = AssetClass::new(mph2, name2); - value = Value::lovelace(n_lovelace) + Value::new(asset_class1, qty1) + Value::new(asset_class2, qty2); - - value.delete_lovelace().flatten() - }` - - compileAndRunMany([ - { - description: "1 lovelace isn't zero", - main: intLovelaceIsZeroScript, - inputs: [int(1)], - output: False - }, - { - description: "0 lovelace is zero", - main: intLovelaceIsZeroScript, - inputs: [int(0)], - output: True - }, - { - description: "lovelace comparison to self is always true", - main: dummyLovelaceEqScript, - inputs: [int(-100)], - output: True - }, - { - description: "1000000 lovelace is equal to 1000000 lovelace", - main: lovelaceEqScript, - inputs: [int(1_000_000), int(1_000_000)], - output: True - }, - { - description: "1000001 lovelace isn't equal to 1000000 lovelace", - main: lovelaceEqScript, - inputs: [int(1_000_001), int(1_000_000)], - output: False - }, - { - description: "lovelace neq comparison with self is always false", - main: dummyLovelaceNeqScript, - inputs: [int(0)], - output: False - }, - { - description: "1_000_000 lovelace neq to 1_000_000 returns false", - main: lovelaceNeqScript, - inputs: [int(1_000_000), int(1_000_000)], - output: False - }, - { - description: "1_000_001 lovelace neq to 1_000_000 returns true", - main: lovelaceNeqScript, - inputs: [int(1_000_001), int(1_000_000)], - output: True - }, - { - description: - "adding zero to 1_000_000 lovelace returns 1_000_000 lovelace", - main: lovelaceAddZeroScript, - inputs: [int(1_000_000)], - output: int(1_000_000) - }, - { - description: - "adding 1 to 1_000_000 lovelace returns 1_000_001 lovelace", - main: lovelaceAddScript, - inputs: [int(1), int(1_000_000)], - output: int(1_000_001) - }, - { - description: - "summing 1 and 1_000_000 lovelace returns 1_000_001 lovelace", - main: lovelaceSumScript, - inputs: [int(1), int(1_000_000)], - output: int(1_000_001) - }, - { - description: - "subtracting zero from 1_000_000 lovelace returns 1_000_000 lovelace", - main: lovelaceSubZeroScript, - inputs: [int(1_000_000)], - output: int(1_000_000) - }, - { - description: "subtracting 1_000_000 from zero returns -1_000_000", - main: zeroSubLovelaceScript, - inputs: [int(1_000_000)], - output: int(-1_000_000) - }, - { - description: "lovelace subtracted from self is always zero", - main: dummyLovelaceSubScript, - inputs: [int(-1_000_001)], - output: True - }, - { - description: "subtracting 1_000_000 from 1_000_001 returns 1", - main: lovelaceSubScript, - inputs: [int(1_000_001), int(1_000_000)], - output: int(1) - }, - { - description: "1_000_000 lovelace multiplied by 1 returns 1_000_000", - main: dummyLovelaceMulScript, - inputs: [int(1_000_000)], - output: int(1_000_000) - }, - { - description: - "1_000_000 lovelace multiplied by 3 returns 3_000_000 lovelace", - main: lovelaceMulScript, - inputs: [int(1_000_000), int(3)], - output: int(3_000_000) - }, - { - description: - "1_000_000 lovelace multipled by 3 after adding an NFT returns 3_000_000 lovelace as well", - main: valueAddMulScript, - inputs: [int(1_000_000), int(1), int(3)], - output: int(3_000_000) - }, - { - description: - "1_000_000 lovelace divided by 1 returns 1_000_000 lovelace", - main: dummyLovelaceDivScript, - inputs: [int(1_000_000)], - output: int(1_000_000) - }, - { - description: "1_000_000 lovelace divided by zero throws error", - main: lovelaceDivScript, - inputs: [int(1_000_000), int(0)], - output: { error: "" } - }, - { - description: - "1_000_000 lovelace divided by -1 returns -1_000_000 lovelace", - main: lovelaceDivScript, - inputs: [int(1_000_000), int(-1)], - output: int(-1_000_000) - }, - { - description: - "3_000_000 lovelace divided by -3 after adding an NFT still returns -1_000_000 lovelace", - main: valueAddDivScript, - inputs: [int(3_000_000), int(1), int(-3)], - output: int(-1_000_000) - }, - { - description: "lovelace >= self always returns true", - main: dummyLovelaceGeqScript, - inputs: [int(1_000_000)], - output: True - }, - { - description: "0 lovelace >= 0 lovelace return true", - main: lovelaceGeqScript, - inputs: [int(0), int(0)], - output: True - }, - { - description: "-1 lovelace >= 0 lovelace return false", - main: lovelaceGeqScript, - inputs: [int(-1), int(0)], - output: False - }, - { - description: "1 lovelace >= 0 lovelace return true", - main: lovelaceGeqScript, - inputs: [int(1), int(0)], - output: True - }, - { - description: "1_000_000 lovelace contains 999_999 lovelace", - main: lovelaceContainsScript, - inputs: [int(1_000_000), int(999_999)], - output: True - }, - { - description: "get_singleton_asset_class doesn't fail for singleton", - main: valueSingletonScript, - inputs: [int(1_000_000), bytes("abcd"), bytes("abcd"), int(1)], - output: True - }, - { - description: - "get_singleton_asset_class doesn't fail for singleton for zero lovelace", - main: valueSingletonScript, - inputs: [int(0), bytes("abcd"), bytes("abcd"), int(1)], - output: True - }, - { - description: "get_singleton_asset_class fails for non-singleton", - main: valueSingletonScript, - inputs: [int(1_000_000), bytes("abcd"), bytes("abcd"), int(2)], - output: { error: "" } - }, - { - description: - "get_singleton_asset_class fails for negative singleton", - main: valueSingletonScript, - inputs: [int(1_000_000), bytes("abcd"), bytes("abcd"), int(-1)], - output: { error: "" } - }, - { - description: "get_singleton_asset_class fails for lovelace only", - main: lovelaceValueSingletonScript, - inputs: [int(1_000_000)], - output: { error: "" } - }, - { - description: - "get_singleton_asset_class fails for two different asset classes", - main: twoAssetClassesValueSingletonScript, - inputs: [ - int(0), - bytes("abcd"), - bytes("abcd"), - int(1), - bytes("abcdef"), - bytes(""), - int(1) - ], - output: { error: "" } - }, - { - description: - "get_singleton_asset_class fails for two different token names", - main: twoAssetClassesValueSingletonScript, - inputs: [ - int(0), - bytes("abcd"), - bytes("abcd"), - int(1), - bytes("abcd"), - bytes(""), - int(1) - ], - output: { error: "" } - }, - { - description: "get_singleton_asset_class works on Valuable.value", - main: valuableSingletonScript, - inputs: [int(1_000_000)], - output: True - }, - { - description: "value.flatten works for 3 entries", - main: valueFlattenScript, - inputs: [ - int(2_000_000), - bytes("abcd"), - bytes("abcd"), - int(1), - bytes("abcdef"), - bytes(""), - int(1) - ], - output: map([ - [assetclass("", ""), int(2_000_000)], - [assetclass("abcd", "abcd"), int(1)], - [assetclass("abcdef", ""), int(1)] - ]) - }, - { - description: "value.flatten works for 0 entries", - main: valueFlattenScript, - inputs: [ - int(0), - bytes("abcd"), - bytes("abcd"), - int(0), - bytes("abcdef"), - bytes(""), - int(0) - ], - output: map([]) - }, - { - description: - "value.delete_policy doesn't change anything for 0 entries", - main: valueDeletePolicyScript, - inputs: [ - int(0), - bytes("abcd"), - bytes("abcd"), - int(0), - bytes("abcdef"), - bytes(""), - int(0), - bytes("") - ], - output: map([]) - }, - { - description: - "value.delete_policy removes multiple tokens with same policy", - main: valueDeletePolicyScript, - inputs: [ - int(1_000_000), - bytes("abcd"), - bytes("abcd"), - int(1), - bytes("abcd"), - bytes(""), - int(1), - bytes("abcd") - ], - output: map([[assetclass("", ""), int(1_000_000)]]) - }, - { - description: "value.delete_policy can delete lovelace", - main: valueDeletePolicyScript, - inputs: [ - int(1_000_000), - bytes("abcd"), - bytes("abcd"), - int(1), - bytes("abcd"), - bytes(""), - int(1), - bytes("") - ], - output: map([ - [assetclass("abcd", "abcd"), int(1)], - [assetclass("abcd", ""), int(1)] - ]) - }, - { - description: "value.delete_lovelace deletes lovelace", - main: valueDeleteLovelaceScript, - inputs: [ - int(1_000_000), - bytes("abcd"), - bytes("abcd"), - int(1), - bytes("abcd"), - bytes(""), - int(1) - ], - output: map([ - [assetclass("abcd", "abcd"), int(1)], - [assetclass("abcd", ""), int(1)] - ]) + func get_singleton_wrapper[V: Valuable](v: V) -> AssetClass { + v.value.get_singleton_asset_class() } - ]) + + func main(lovelace: Int) -> Bool { + pub_key_hash_bytes = #01234567890123456789012345678901234567890123456789012345; + address = Address::new(SpendingCredential::new_pubkey(PubKeyHash::new(pub_key_hash_bytes)), Option[StakingCredential]::None); + asset_class = AssetClass::new(MintingPolicyHash::new(#abcd), #abcd); + value = Value::lovelace(lovelace) + Value::new(asset_class, 1); + output = TxOutput::new( + address, + value, + TxOutputDatum::new_none() + ); + + get_singleton_wrapper(output) == asset_class + }`) + + it("ok on Valuable.value", () => { + runner2([int(1_000_000)], True) + }) + }) + + describe("Value.flatten", () => { + const runner = compileForRun(`testing value_flatten + func main(n_lovelace: Int, mph1: MintingPolicyHash, name1: ByteArray, qty1: Int, mph2: MintingPolicyHash, name2: ByteArray, qty2: Int) -> Map[AssetClass]Int { + asset_class1 = AssetClass::new(mph1, name1); + asset_class2 = AssetClass::new(mph2, name2); + value = Value::lovelace(n_lovelace) + Value::new(asset_class1, qty1) + Value::new(asset_class2, qty2); + + value.flatten() + }`) + + it("ok for 0 entries", () => { + runner( + [ + int(0), + bytes("abcd"), + bytes("abcd"), + int(0), + bytes("abcdef"), + bytes(""), + int(0) + ], + map([]) + ) + }) + + it("ok for 3 entries", () => { + runner( + [ + int(2_000_000), + bytes("abcd"), + bytes("abcd"), + int(1), + bytes("abcdef"), + bytes(""), + int(1) + ], + map([ + [assetclass("", ""), int(2_000_000)], + [assetclass("abcd", "abcd"), int(1)], + [assetclass("abcdef", ""), int(1)] + ]) + ) + }) + }) + + describe("Value.delete_policy", () => { + const runner = compileForRun(`testing value_delete_policy + func main( + n_lovelace: Int, + mph1: MintingPolicyHash, + name1: ByteArray, + qty1: Int, + mph2: MintingPolicyHash, + name2: ByteArray, + qty2: Int, + mph_to_delete: MintingPolicyHash + ) -> Map[AssetClass]Int { + asset_class1 = AssetClass::new(mph1, name1); + asset_class2 = AssetClass::new(mph2, name2); + value = Value::lovelace(n_lovelace) + Value::new(asset_class1, qty1) + Value::new(asset_class2, qty2); + + value.delete_policy(mph_to_delete).flatten() + }`) + + it("doesn't change anything for 0 entries", () => { + runner( + [ + int(0), + bytes("abcd"), + bytes("abcd"), + int(0), + bytes("abcdef"), + bytes(""), + int(0), + bytes("") + ], + map([]) + ) + }) + + it("removes multiple tokens with same policy", () => { + runner( + [ + int(1_000_000), + bytes("abcd"), + bytes("abcd"), + int(1), + bytes("abcd"), + bytes(""), + int(1), + bytes("abcd") + ], + map([[assetclass("", ""), int(1_000_000)]]) + ) + }) + + it("can delete lovelace", () => { + runner( + [ + int(1_000_000), + bytes("abcd"), + bytes("abcd"), + int(1), + bytes("abcd"), + bytes(""), + int(1), + bytes("") + ], + map([ + [assetclass("abcd", "abcd"), int(1)], + [assetclass("abcd", ""), int(1)] + ]) + ) + }) + }) + + describe("Value.delete_lovelace", () => { + const runner = compileForRun(`testing value_delete_lovelace + func main( + n_lovelace: Int, + mph1: MintingPolicyHash, + name1: ByteArray, + qty1: Int, + mph2: MintingPolicyHash, + name2: ByteArray, + qty2: Int + ) -> Map[AssetClass]Int { + asset_class1 = AssetClass::new(mph1, name1); + asset_class2 = AssetClass::new(mph2, name2); + value = Value::lovelace(n_lovelace) + Value::new(asset_class1, qty1) + Value::new(asset_class2, qty2); + + value.delete_lovelace().flatten() + }`) + + it("deletes lovelace from value with two tokens", () => { + runner( + [ + int(1_000_000), + bytes("abcd"), + bytes("abcd"), + int(1), + bytes("abcd"), + bytes(""), + int(1) + ], + map([ + [assetclass("abcd", "abcd"), int(1)], + [assetclass("abcd", ""), int(1)] + ]) + ) + }) + }) + + describe("Value::from_flat", () => { + const runner = compileForRun(`testing value_from_flat_map + func main(mp: Map[AssetClass]Int) -> Value { + Value::from_flat(mp) + }`) + + it("ok for empty map", () => { + runner([map([])], map([])) + }) + + it("ok for a single entry", () => { + runner( + [map([[assetclass("abcd", "abcd"), int(1)]])], + map([[bytes("abcd"), map([[bytes("abcd"), int(1)]])]]) + ) + }) + + it("throws error for duplicate entry", () => { + runner( + [ + map([ + [assetclass("abcd", "abcd"), int(1)], + [assetclass("abcd", "abcd"), int(1)] + ]) + ], + { error: "" } + ) + }) + + it("ok for two tokens with the same policy (sorted output)", () => { + runner( + [ + map([ + [assetclass("abcd", "abcd"), int(1)], + [assetclass("abcd", ""), int(1)] + ]) + ], + map([ + [ + bytes("abcd"), + map([ + [bytes(""), int(1)], + [bytes("abcd"), int(1)] + ]) + ] + ]) + ) + }) + + it("ok for two tokens with the same policy and another third token (sorted output)", () => { + runner( + [ + map([ + [assetclass("abcd", "abcd"), int(1)], + [assetclass("abcd", ""), int(1)], + [assetclass("", ""), int(1_000_000)] + ]) + ], + map([ + [bytes(""), map([[bytes(""), int(1_000_000)]])], + [ + bytes("abcd"), + map([ + [bytes(""), int(1)], + [bytes("abcd"), int(1)] + ]) + ] + ]) + ) + }) + + it("ok for two tokens with same policy and another third token before", () => { + runner( + [ + map([ + [assetclass("", ""), int(1_000_000)], + [assetclass("abcd", "abcd"), int(1)], + [assetclass("abcd", ""), int(1)] + ]) + ], + map([ + [bytes(""), map([[bytes(""), int(1_000_000)]])], + [ + bytes("abcd"), + map([ + [bytes(""), int(1)], + [bytes("abcd"), int(1)] + ]) + ] + ]) + ) + }) + + it("fails for duplicate and another third token before", () => { + runner( + [ + map([ + [assetclass("", ""), int(1_000_000)], + [assetclass("abcd", "abcd"), int(1)], + [assetclass("abcd", "abcd"), int(1)] + ]) + ], + { error: "" } + ) + }) + + it("ok for two tokens with same policy and another third token that sorts after", () => { + runner( + [ + map([ + [assetclass("abcd", "abcd"), int(1)], + [assetclass("abcd", ""), int(1)], + [assetclass("ef01", ""), int(2)] + ]) + ], + map([ + [ + bytes("abcd"), + map([ + [bytes(""), int(1)], + [bytes("abcd"), int(1)] + ]) + ], + [bytes("ef01"), map([[bytes(""), int(2)]])] + ]) + ) + }) + + it("fails for two tokens with same policy but different name, and other pair with same asset class that sorts after", () => { + runner( + [ + map([ + [assetclass("ef01", ""), int(2)], + [assetclass("abcd", "abcd"), int(1)], + [assetclass("abcd", ""), int(1)], + [assetclass("ef01", ""), int(2)] + ]) + ], + { error: "" } + ) + }) + }) })