diff --git a/CLVMDotNet/src/CLVM/CLVMObject.cs b/CLVMDotNet/src/CLVM/CLVMObject.cs index 9e9b7e7..a85a42e 100644 --- a/CLVMDotNet/src/CLVM/CLVMObject.cs +++ b/CLVMDotNet/src/CLVM/CLVMObject.cs @@ -31,7 +31,7 @@ public CLVMObject(dynamic? v) //to make sure if a tuple is used, it cannot have more than 2 items in it. var type = v?.GetType(); var s = type?.GetGenericArguments(); - if (s.Length > 2) + if (s?.Length > 2) { throw new ArgumentException("tuples must be of size 2"); } diff --git a/CLVMDotNet/src/CLVM/Casts.cs b/CLVMDotNet/src/CLVM/Casts.cs index 392a111..11ffcd0 100644 --- a/CLVMDotNet/src/CLVM/Casts.cs +++ b/CLVMDotNet/src/CLVM/Casts.cs @@ -1,3 +1,4 @@ +using System.Net.Sockets; using System.Numerics; namespace CLVMDotNet.CLVM @@ -15,35 +16,101 @@ public static BigInteger IntFromBytes(byte[] blob) return new BigInteger(blob, isBigEndian: true); } + /// + /// In python integers are dynamically sized, so working out the number of bytes required in + /// c# needs to first see if the number will fit into a number of datatypes. + /// + /// 0-255 (byte) + /// + /// + /// This is required to see if the biginteger parameter is able to fit into a smaller + /// datatype and converting them to bytes. + /// + /// + /// public static byte[] IntToBytes(BigInteger v) { - byte[] byteArray = v.ToByteArray(); + if (v == 0) + { + byte[] emptyByteArray = new byte[0]; + return emptyByteArray; + } + + //v can fit into a byte 0-255 (unsigned) + if (v >= byte.MinValue && v <= byte.MaxValue) + { + var intValue = (byte)v; + var b = (byte)0x00; + if (v < 128) + { + byte[] byteArray = new[] { intValue }; + return byteArray; + } + else + { + byte[] byteArray = new[] { b,intValue }; + return byteArray; + } + } - if (BitConverter.IsLittleEndian) + //v can fit into an sbyte -128 to 127 (signed) + if (v >= sbyte.MinValue && v <= sbyte.MaxValue) { - byteArray = byteArray.Reverse().ToArray(); + sbyte sbyteValue = (sbyte)v; + byte byteValue = (byte)sbyteValue; + byte[] byteArray = new[] { byteValue }; + return byteArray; } - while (byteArray.Length > 1 && (byteArray[0] == 0xFF || byteArray[0] == 0x00)) + //v can fit into a short -32,768 to 32,767 (signed) + if (v >= short.MinValue && v <= short.MaxValue) { - byteArray = byteArray.Skip(1).ToArray(); + short shortValue = (short)v; + byte[] byteArray = BitConverter.GetBytes(shortValue); + Array.Reverse(byteArray); + return byteArray; } - if (!v.IsZero) + //v can fit into a long -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807) + if (v >= long.MinValue && v <= long.MaxValue) { - if (byteArray[0] >= 0x80) + if (v == 0) { - byteArray = new byte[] { 0 }.Concat(byteArray).ToArray(); + return new byte[0]; } + + byte[] result = v.ToByteArray(); + + // if (result[0] == 0x00) + // { + // // Remove leading 0x00 byte if present + // byte[] minimalResult = new byte[result.Length - 1]; + // Array.Copy(result, 1, minimalResult, 0, minimalResult.Length); + // return minimalResult; + // } + result = result.Reverse().ToArray(); + return result; } + //python equivalent of numbers larger than a long is a bigInteger else { - byteArray = new byte[0]; - } + byte[] byteArray = v.ToByteArray(); + + if (BitConverter.IsLittleEndian) + { + byteArray = byteArray.Reverse().ToArray(); + } - return byteArray; + while (byteArray.Length > 1 && (byteArray[0] == 0xFF || byteArray[0] == 0x00)) + { + byteArray = byteArray.Skip(1).ToArray(); + } + + return byteArray; + } } + public static int LimbsForInt(BigInteger v) { return IntToBytes(v).Length; diff --git a/CLVMDotNet/src/CLVM/CoreOps.cs b/CLVMDotNet/src/CLVM/CoreOps.cs index b42d16f..ad27a54 100644 --- a/CLVMDotNet/src/CLVM/CoreOps.cs +++ b/CLVMDotNet/src/CLVM/CoreOps.cs @@ -1,8 +1,121 @@ +using System.Numerics; + namespace CLVMDotNet.CLVM { public class CoreOps { - public static Tuple OpIf(SExp args) + + public static IEnumerable ArgsLen(string opName, SExp args) + { + foreach (var arg in args.AsIter()) + { + if (arg.Pair != null) + { + throw new EvalError(string.Format("{0} requires int args", opName), arg); + } + yield return arg.AsAtom().Length; + } + } + + public static Tuple OpDefaultUnknown(byte[] op, SExp args) + { + // Any opcode starting with 0xFFFF is reserved (i.e., fatal error). + // Opcodes are not allowed to be empty. + if (op.Length == 0 || (op[0] == 0xFF && op[1] == 0xFF)) + { + throw new EvalError("reserved operator", SExp.To(op)); + } + + // All other unknown opcodes are no-ops. + // The cost of the no-ops is determined by the opcode number, except the + // 6 least significant bits. + + int costFunction = (op[op.Length - 1] & 0b11000000) >> 6; + // The multiplier cannot be 0. It starts at 1. + + if (op.Length > 5) + { + throw new EvalError("invalid operator", SExp.To(op)); + } + + BigInteger costMultiplier = new BigInteger(op.Take(op.Length - 1).ToArray()) + 1; + + // 0 = constant + // 1 = like op_add/op_sub + // 2 = like op_multiply + // 3 = like op_concat + BigInteger cost; + switch (costFunction) + { + case 0: + cost = 1; + break; + case 1: + // Like op_add + cost = Costs.ARITH_BASE_COST; + int argSize = 0; + foreach (int l in ArgsLen("unknown op", args)) + { + argSize += l; + cost += Costs.ARITH_COST_PER_ARG; + } + + cost += argSize * Costs.ARITH_COST_PER_BYTE; + break; + case 2: + // Like op_multiply + cost = Costs.MUL_BASE_COST; + var operands = ArgsLen("unknown op", args).GetEnumerator(); + try + { + int vs = operands.MoveNext() ? operands.Current : 0; + while (operands.MoveNext()) + { + int rs = operands.Current; + cost += Costs.MUL_COST_PER_OP; + cost += (rs + vs) * Costs.MUL_LINEAR_COST_PER_BYTE; + cost += (rs * vs) / Costs.MUL_SQUARE_COST_PER_BYTE_DIVIDER; + // This is an estimate, since we don't want to actually multiply the values. + vs += rs; + } + } + catch (Exception) + { + // Handle StopIteration + } + + break; + case 3: + // Like concat + cost = Costs.CONCAT_BASE_COST; + int length = 0; + foreach (var arg in args.AsIter()) + { + if (arg.Pair != null) + { + throw new EvalError("unknown op on list", arg); + } + + cost += Costs.CONCAT_COST_PER_ARG; + length += arg.Atom.Length; + } + + cost += length * Costs.CONCAT_COST_PER_BYTE; + break; + default: + throw new EvalError("invalid operator", SExp.To(op)); + } + + cost *= (int)costMultiplier; + if (cost >= (BigInteger)1 << 32) + { + throw new EvalError("invalid operator", SExp.To(op)); + } + + return Tuple.Create(cost, SExp.NULL); + } + + public static Tuple OpIf(SExp args) { if (args.ListLength() != 3) { @@ -12,53 +125,53 @@ public static Tuple OpIf(SExp args) SExp r = args.Rest(); if (args.First().Nullp()) { - return new Tuple(Costs.IF_COST, r.Rest().First()); + return new Tuple(Costs.IF_COST, r.Rest().First()); } - return new Tuple(Costs.IF_COST, r.First()); + return new Tuple(Costs.IF_COST, r.First()); } - public static Tuple OpCons(SExp args) + public static Tuple OpCons(SExp args) { if (args.ListLength() != 2) { throw new EvalError("c takes exactly 2 arguments", args); } - return new Tuple(Costs.CONS_COST, args.First().Cons(args.Rest().First())); + return new Tuple(Costs.CONS_COST, args.First().Cons(args.Rest().First())); } - public static Tuple OpFirst(SExp args) + public static Tuple OpFirst(SExp args) { if (args.ListLength() != 1) { throw new EvalError("f takes exactly 1 argument", args); } - return new Tuple(Costs.FIRST_COST, args.First().First()); + return new Tuple(Costs.FIRST_COST, args.First().First()); } - public static Tuple OpRest(SExp args) + public static Tuple OpRest(SExp args) { if (args.ListLength() != 1) { throw new EvalError("r takes exactly 1 argument", args); } - return new Tuple(Costs.REST_COST, args.First().Rest()); + return new Tuple(Costs.REST_COST, args.First().Rest()); } - public static Tuple OpListp(SExp args) + public static Tuple OpListp(SExp args) { if (args.ListLength() != 1) { throw new EvalError("l takes exactly 1 argument", args); } - return new Tuple(Costs.LISTP_COST, args.First().Listp() ? SExp.True : SExp.False); + return new Tuple(Costs.LISTP_COST, args.First().Listp() ? SExp.True : SExp.False); } - public static Tuple OpRaise(SExp args) + public static Tuple OpRaise(SExp args) { if (args.ListLength() == 1 && !args.First().Listp()) { @@ -70,7 +183,7 @@ public static Tuple OpRaise(SExp args) } } - public static Tuple OpEq(SExp args) + public static Tuple OpEq(SExp args) { if (args.ListLength() != 2) { @@ -88,10 +201,10 @@ public static Tuple OpEq(SExp args) byte[] b0 = a0.AsAtom(); byte[] b1 = a1.AsAtom(); - int cost = Costs.EQ_BASE_COST; - cost += (b0.Length + b1.Length) * Costs.EQ_COST_PER_BYTE; + BigInteger cost = Costs.EQ_BASE_COST; + cost += (b0!.Length + b1!.Length) * Costs.EQ_COST_PER_BYTE; - return new Tuple(cost, b0.Equals(b1) ? SExp.True : SExp.False); + return Tuple.Create(cost, b0.SequenceEqual(b1) ? SExp.True : SExp.False); } } } \ No newline at end of file diff --git a/CLVMDotNet/src/CLVM/HelperFunctions.cs b/CLVMDotNet/src/CLVM/HelperFunctions.cs index 95438b4..df62a6b 100644 --- a/CLVMDotNet/src/CLVM/HelperFunctions.cs +++ b/CLVMDotNet/src/CLVM/HelperFunctions.cs @@ -125,7 +125,9 @@ private static bool IsTuple(dynamic? obj) continue; } - else if (value != null && value.GetType().IsArray && value is not byte[]) + if (value is System.Collections.IList list && + list.GetType().IsGenericType && + list.GetType().GetGenericTypeDefinition() == typeof(List<>)) { target = stack.Count; stack.Add(new CLVMObject(nullBytes)); @@ -205,6 +207,10 @@ public static byte[] ConvertAtomToBytes(dynamic? v) { return Encoding.UTF8.GetBytes(str); } + if (v is string[] strarray && strarray.Length == 1) + { + return Encoding.UTF8.GetBytes(strarray[0]); + } if (v is int intValue) { diff --git a/CLVMDotNet/src/CLVM/MoreOps.cs b/CLVMDotNet/src/CLVM/MoreOps.cs index ff95145..b616732 100644 --- a/CLVMDotNet/src/CLVM/MoreOps.cs +++ b/CLVMDotNet/src/CLVM/MoreOps.cs @@ -1,23 +1,28 @@ using System.Numerics; using System.Security.Cryptography; +using System.Text; +using chia.dotnet.bls; +using dotnetstandard_bip39; namespace CLVMDotNet.CLVM { public static class MoreOps { - private const int MALLOC_COST_PER_BYTE = 1; - private const int SHA256_BASE_COST = 1; // Define other constants as needed. - public static Tuple MallocCost(BigInteger cost, SExp atom) { - BigInteger newCost = cost + atom.AsAtom().Length * MALLOC_COST_PER_BYTE; + var a = atom.AsAtom(); + var length = a != null ? a.Length : 0; + + BigInteger newCost = cost + length * Costs.MALLOC_COST_PER_BYTE; return Tuple.Create(newCost, atom); } + public static Tuple OpSha256(SExp args) { - int cost = SHA256_BASE_COST; + int cost = Costs.SHA256_BASE_COST; int argLen = 0; + byte[] result = Array.Empty(); using (SHA256 sha256 = SHA256.Create()) { foreach (SExp arg in args.AsIter()) @@ -30,18 +35,16 @@ public static Tuple OpSha256(SExp args) argLen += atom.Length; cost += Costs.SHA256_COST_PER_ARG; - sha256.TransformBlock(atom, 0, atom.Length, null, 0); + result = sha256.ComputeHash(atom); + cost += argLen * Costs.SHA256_COST_PER_BYTE; } - sha256.TransformFinalBlock(Array.Empty(), 0, 0); - byte[] result = sha256.Hash; - cost += argLen * Costs.SHA256_COST_PER_BYTE; - - return MallocCost(cost, SExp.To(result)); + var sexp = SExp.To(result); + return MallocCost(cost, sexp); } } - public static IEnumerable<(BigInteger, int)> ArgsAsInts(string opName, SExp args) + public static IEnumerable<(BigInteger, BigInteger)> ArgsAsInts(string opName, SExp args) { foreach (SExp arg in args.AsIter()) { @@ -51,7 +54,7 @@ public static Tuple OpSha256(SExp args) } BigInteger intValue = arg.AsInt(); - int atomLength = arg.AsAtom().Length; + int atomLength = arg.AsAtom()!.Length; yield return (intValue, atomLength); } @@ -61,12 +64,13 @@ public static IEnumerable ArgsAsInt32(string opName, SExp args) { foreach (SExp arg in args.AsIter()) { - if (arg != null) + if (arg.Pair != null) { throw new EvalError($"{opName} requires int32 args", arg); } - if (arg.AsAtom().Length > 4) + var atom = arg.AsAtom(); + if (atom?.Length > 4) { throw new EvalError($"{opName} requires int32 args (with no leading zeros)", arg); } @@ -77,14 +81,18 @@ public static IEnumerable ArgsAsInt32(string opName, SExp args) public static List<(BigInteger, BigInteger)> ArgsAsIntList(string opName, dynamic args, int count) { - var intList = ArgsAsInts(opName, args); - if (intList.Count != count) + List<(BigInteger, BigInteger)> result = new List<(BigInteger, BigInteger)>(); + foreach (var intList in ArgsAsInts(opName, args)) { - string plural = count != 1 ? "s" : ""; - throw new EvalError($"{opName} takes exactly {count} argument{plural}", args); + result.Add(intList); } - return intList; + if (result.Count != count) + { + throw new EvalError($"{opName} takes exactly {count} arguments", args); + } + + return result; } public static IEnumerable ArgsAsBools(string opName, SExp args) @@ -108,13 +116,70 @@ public static List ArgsAsBoolList(string opName, SExp args, int count) List boolList = ArgsAsBools(opName, args).ToList(); if (boolList.Count != count) { - string plural = count != 1 ? "s" : ""; - throw new EvalError($"{opName} takes exactly {count} argument{plural}", args); + throw new EvalError($"{opName} takes exactly {count} arguments", args); } return boolList; } + public static Tuple OpSubtract(SExp args) + { + BigInteger cost = Costs.ARITH_BASE_COST; + + if (args.Nullp()) + { + return MallocCost(cost, SExp.To(0)); + } + + BigInteger sign = 1; + BigInteger total = 0; + BigInteger arg_size = 0; + + foreach (var pair in ArgsAsInts("-", args)) + { + var r = pair.Item1; + var l = pair.Item2; + + total += sign * r; + sign = -1; + arg_size += l; + cost += Costs.ARITH_COST_PER_ARG; + } + + cost += arg_size * Costs.ARITH_COST_PER_BYTE; + return MallocCost(cost, SExp.To(total)); + } + + + public static Tuple OpMultiply(SExp args) + { + BigInteger cost = Costs.MUL_BASE_COST; + var operands = ArgsAsInts("*", args); + + try + { + var firstOperand = operands.First(); + var v = firstOperand.Item1; + var vs = firstOperand.Item2; + + foreach (var (r, rs) in operands.Skip(1)) + { + cost += Costs.MUL_COST_PER_OP; + cost += (rs + vs) * Costs.MUL_LINEAR_COST_PER_BYTE; + cost += (rs * vs) / Costs.MUL_SQUARE_COST_PER_BYTE_DIVIDER; + v = v * r; + vs = (v); // Assuming limbs_for_int function is defined + } + + return MallocCost(cost, SExp.To(v)); // Assuming malloc_cost and args.to functions are defined + } + catch (InvalidOperationException) + { + return MallocCost(cost, SExp.To(1)); // Assuming malloc_cost and args.to functions are defined + } + } + + public static Tuple OpAdd(SExp args) { BigInteger total = 0; @@ -131,9 +196,9 @@ public static Tuple OpAdd(SExp args) return MallocCost(cost, SExp.To(total)); } - public static (BigInteger, SExp) OpDivmod(SExp args) + public static Tuple OpDivmod(SExp args) { - BigInteger cost = Costs.DIV_BASE_COST; + BigInteger cost = Costs.DIVMOD_BASE_COST; var (i0, l0) = ArgsAsIntList("divmod", args, 2)[0]; var (i1, l1) = ArgsAsIntList("divmod", args, 2)[1]; if (i1 == 0) @@ -141,12 +206,13 @@ public static (BigInteger, SExp) OpDivmod(SExp args) throw new EvalError("divmod with 0", SExp.To(i0)); } - cost += (l0 + l1) * Costs.DIV_COST_PER_BYTE; + cost += (l0 + l1) * Costs.DIVMOD_COST_PER_BYTE; BigInteger q = BigInteger.DivRem(i0, i1, out BigInteger r); SExp q1 = SExp.To(q); SExp r1 = SExp.To(r); cost += (q1.Atom.Length + r1.Atom.Length) * Costs.MALLOC_COST_PER_BYTE; - return (cost, SExp.To(new List { q1, r1 })); + var ex = SExp.To((q1, r1)); + return MallocCost(cost, ex); } public static Tuple OpDiv(SExp args) @@ -170,77 +236,115 @@ public static Tuple OpDiv(SExp args) return MallocCost(cost, SExp.To(q)); } - // public (BigInteger, SExp) OpGr(SExp args) - // { - // var (i0, l0) = ArgsAsIntList(">", args, 2); - // BigInteger cost = Costs.GR_BASE_COST; - // cost += (l0 + ArgsAsIntList(">", args, 2)[1].Item2) * Costs.GR_COST_PER_BYTE; - // return (cost, i0 > ArgsAsIntList(">", args, 2)[1].Item1 ? SExp.True : SExp.False); - // } -// -// public (BigInteger, SExp) OpGrBytes(dynamic args) -// { -// var argList = args.AsList(); -// if (argList.Count != 2) -// { -// throw new EvalError(">s takes exactly 2 arguments", args); -// } -// var a0 = argList[0]; -// var a1 = argList[1]; -// if (a0.IsPair || a1.IsPair) -// { -// throw new EvalError(">s on list", a0.IsPair ? a0 : a1); -// } -// var b0 = a0.AsAtom(); -// var b1 = a1.AsAtom(); -// BigInteger cost = Costs.GRS_BASE_COST; -// cost += (b0.Length + b1.Length) * Costs.GRS_COST_PER_BYTE; -// return (cost, b0.CompareTo(b1) > 0 ? args.True : args.False); -// } -// -// public (BigInteger, SExp) OpPubkeyForExp(SExp args) -// { -// var (i0, l0) = ArgsAsIntList("pubkey_for_exp", args, 1)[0]; -// i0 %= BigInteger.Parse("0x73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001"); -// Exponent exponent = PrivateKey.FromBytes(i0.ToByteArray()); -// try -// { -// G1.Generator.ToBytes(); -// SExp r = args.To(exponent.GetG1().ToByteArray()); -// BigInteger cost = Costs.PUBKEY_BASE_COST; -// cost += l0 * Costs.PUBKEY_COST_PER_BYTE; -// return MallocCost(cost, r); -// } -// catch (Exception ex) -// { -// throw new EvalError($"problem in op_pubkey_for_exp: {ex}", args); -// } -// } -// -// public (BigInteger, SExp) OpPointAdd(dynamic items) -// { -// BigInteger cost = Costs.POINT_ADD_BASE_COST; -// G1 p = new G1(); -// -// foreach (var item in items.AsEnumerable()) -// { -// if (item.IsPair) -// { -// throw new EvalError("point_add on list", item); -// } -// try -// { -// p += G1.FromBytes(item.AsAtom()); -// cost += Costs.POINT_ADD_COST_PER_ARG; -// } -// catch (Exception ex) -// { -// throw new EvalError($"point_add expects blob, got {item}: {ex}", items); -// } -// } -// return MallocCost(cost, items.To(p.ToBytes())); -// } -// + public static Tuple OpGr(SExp args) + { + var list = ArgsAsIntList(">", args, 2).ToArray(); + var (i0, l0) = (list[0].Item1, list[0].Item2); + var (i1, l1) = (list[1].Item1, list[1].Item2); + + BigInteger cost = Costs.GR_BASE_COST; + cost += (l0 + l1) * Costs.GR_COST_PER_BYTE; + if (i0 > i1) + return Tuple.Create(cost, SExp.True); + + return Tuple.Create(cost, SExp.False); + } + + + public static Tuple OpGrBytes(SExp args) + { + var argList = args.AsIter().ToList(); + if (argList.Count != 2) + { + throw new EvalError(">s takes exactly 2 arguments", args); + } + + var a0 = argList[0]; + var a1 = argList[1]; + if (a0.Pair != null || a1.Pair != null) + { + throw new EvalError(">s on list", a0.Pair != null ? a0 : a1); + } + + var b0 = a0.AsAtom(); + var b1 = a1.AsAtom(); + BigInteger cost = Costs.GRS_BASE_COST; + cost += (b0.Length + b1.Length) * Costs.GRS_COST_PER_BYTE; + + int comparisonResult = b0.AsSpan().SequenceCompareTo(b1.AsSpan()); + + return Tuple.Create(cost, comparisonResult > 0 ? SExp.True : SExp.False); + } + + public static Tuple OpPubkeyForExp(SExp args) + { + var (i0, l0) = ArgsAsIntList("pubkey_for_exp", args, 1)[0]; + string hexValue = "73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001"; + BigInteger largeNumber = BigInteger.Parse(hexValue, System.Globalization.NumberStyles.HexNumber); + + // Ensure i0 is positive + if (i0 < 0) + { + i0 += largeNumber; // Add largeNumber to make it positive + } + i0 %= largeNumber; + + var keyBytes = BigIntegerToBytesWithLittleEndian(i0); + var sk = PrivateKey.FromBytes(keyBytes); + try + { + var g1 = sk.GetG1().ToBytes(); + SExp r = SExp.To(g1); + BigInteger cost = Costs.PUBKEY_BASE_COST; + cost += l0 * Costs.PUBKEY_COST_PER_BYTE; + return MallocCost(cost, r); + } + catch (Exception ex) + { + throw new EvalError($"problem in op_pubkey_for_exp: {ex}", args); + } + + static byte[] BigIntegerToBytesWithLittleEndian(BigInteger v) + { + byte[] byteArray = v.ToByteArray(); + byteArray = byteArray.Reverse().ToArray(); + if (byteArray.Length < 32) + { + byte[] paddedArray = new byte[32]; + Array.Copy(byteArray, 0, paddedArray, 32 - byteArray.Length, byteArray.Length); + byteArray = paddedArray; + } + + return byteArray; + } + } + + + public static Tuple OpPointAdd(dynamic items) + { + throw new Exception("Not implemented Exception"); + // BigInteger cost = Costs.POINT_ADD_BASE_COST; + // var g1 = new JacobianPoint(); + // + // foreach (var item in items.AsEnumerable()) + // { + // if (item.IsPair) + // { + // throw new EvalError("point_add on list", item); + // } + // try + // { + // p += G1.FromBytes(item.AsAtom()); + // cost += Costs.POINT_ADD_COST_PER_ARG; + // } + // catch (Exception ex) + // { + // throw new EvalError($"point_add expects blob, got {item}: {ex}", items); + // } + // } + // return MallocCost(cost, items.To(p.ToBytes())); + } + public static Tuple OpStrlen(SExp args) { if (args.ListLength() != 1) @@ -275,27 +379,57 @@ public static Tuple OpSubstr(SExp args) var s0 = a0.AsAtom(); - int i1, i2; + BigInteger i1 = 0, i2 = 0; + IEnumerable lst = ArgsAsInt32("substr", args.Rest()); + int charsToTake = 0; if (argCount == 2) { - i1 = ArgsAsInt32("substr", args.Rest()).Single(); - i2 = s0.Length; + //substring starting at index, take the rest of the string + var array = lst.ToArray(); + i1 = array[0]; + i2 = 0; } else { - var intArgs = ArgsAsInt32("substr", args.Rest()).ToArray(); - i1 = intArgs[0]; - i2 = intArgs[1]; + //substring starting at index, take x amount of characters + var array = lst.ToArray(); + i1 = array[0]; + i2 = (int)array[1]; + ; } - if (i2 > s0.Length || i2 < i1 || i2 < 0 || i1 < 0) + if (i2 > s0.Length || i2 < 0 || i1 > s0.Length || i1 < 0 || (argCount > 2 && i2 < i1)) { throw new EvalError("invalid indices for substr", args); } - var s = s0.SubString(i1, i2 - i1); - BigInteger cost = 1; - return MallocCost(cost, SExp.To(s)); + //much easier to work with strings + if (s0 is byte[] arr) + { + var startIndex = (int)(i2 - i1) + 1; + //if there isn't a second int to use to substring i.e. how many characters to take + //take them all + if (i2 == 0) + { + string text = Encoding.UTF8.GetString(arr); + var s = text.Substring((int)i1); + BigInteger cost = 1; + return Tuple.Create(cost, SExp.To(s)); + } + else + { + var endIndex = (int)(i2 - i1); + string text = Encoding.UTF8.GetString(arr); + var s = text.Substring((int)i1, endIndex); + BigInteger cost = 1; + return Tuple.Create(cost, SExp.To(s)); + } + } + else + { + BigInteger cost = 1; + return Tuple.Create(cost, SExp.To(s0)); + } } public static Tuple OpConcat(SExp args) @@ -319,74 +453,87 @@ public static Tuple OpConcat(SExp args) return MallocCost(cost, SExp.To(r)); } - // public static Tuple OpAsh(SExp args) - // { - // var (i0, l0) = ArgsAsIntList("ash", args, 2); - // var (i1, l1) = ArgsAsIntList("ash", args.Rest(), 1).First(); - // - // if (l1 > 4) - // { - // throw new EvalError("ash requires int32 args (with no leading zeros)", args.Rest().First()); - // } - // - // if (Math.Abs(i1) > 65535) - // { - // throw new EvalError("shift too large", args.To(i1)); - // } - // - // BigInteger r; - // - // if (i1 >= 0) - // { - // r = i0 << (int)i1; - // } - // else - // { - // r = i0 >> (int)-i1; - // } - // - // BigInteger cost = Costs.ASHIFT_BASE_COST; - // cost += (l0 + Casts.LimbsForInt(r)) * Costs.ASHIFT_COST_PER_BYTE; - // - // return MallocCost(cost, SExp.To(r)); - // } -// -// public (BigInteger, SExp) OpLsh(SExp args) -// { -// var (i0, l0) = ArgsAsIntList("lsh", args, 2); -// var (i1, l1) = ArgsAsIntList("lsh", args.Rest(), 1).First(); -// -// if (l1 > 4) -// { -// throw new EvalError("lsh requires int32 args (with no leading zeros)", args.Rest().First()); -// } -// -// if (Math.Abs(i1) > 65535) -// { -// throw new EvalError("shift too large", args.To(i1)); -// } -// -// // We actually want i0 to be an unsigned int -// var a0 = args.First().AsAtom(); -// var i0Bytes = a0.Reverse().ToArray(); // Reverse bytes for little-endian representation -// var i0 = new BigInteger(i0Bytes); -// -// BigInteger r; -// -// if (i1 >= 0) -// { -// r = i0 << (int)i1; -// } -// else -// { -// r = i0 >> (int)-i1; -// } -// -// BigInteger cost = Costs.LSHIFT_BASE_COST; -// cost += (l0 + Casts.LimbsForInt(r)) * Costs.LSHIFT_COST_PER_BYTE; -// -// return (cost, args.To(r)); -// } + public static Tuple OpAsh(SExp args) + { + var list = ArgsAsIntList("ash", args, 2).ToArray(); + var i0 = list[0].Item1; + var l0 = list[0].Item2; + + var i1 = list[1].Item1; + var l1 = list[1].Item2; + + if (l1 > 4) + { + throw new EvalError("ash requires int32 args (with no leading zeros)", args.Rest().First()); + } + + if (i1 > 65535) + { + throw new EvalError("shift too large", SExp.To(i1)); + } + + BigInteger r; + + if (i1 >= 0) + { + r = i0 << (int)i1; + } + else + { + r = i0 >> (int)-i1; + } + + BigInteger cost = Costs.ASHIFT_BASE_COST; + cost += (l0 + Casts.LimbsForInt(r)) * Costs.ASHIFT_COST_PER_BYTE; + + return MallocCost(cost, SExp.To(r)); + } + + public static Tuple OpLsh(SExp args) + { + var list = ArgsAsIntList("lsh", args, 2).ToArray(); + var i0 = list[0].Item1; + var l0 = list[0].Item2; + var i1 = list[1].Item1; + var l1 = list[1].Item2; + + if (l1 > 4) + { + throw new EvalError("lsh requires int32 args (with no leading zeros)", args.Rest().First()); + } + + if (i1 > 65535) + { + throw new EvalError("shift too large", SExp.To(i1)); + } + + // We actually want i0 to be an unsigned int + byte[] a0 = args.First().AsAtom(); + var i0Bytes = a0; + if (BitConverter.IsLittleEndian) + { + // Reverse the byte array for big-endian interpretation + Array.Reverse(i0Bytes); + } + + i0 = new BigInteger(i0Bytes); + + BigInteger r = 0; + + if (i1 >= 0) + { + r = i0 << (int)i1; + } + else + { + r = (int)i0 >> (int)-i1; + } + + BigInteger cost = Costs.LSHIFT_BASE_COST; + cost += (l0 + Casts.LimbsForInt(r)) * Costs.LSHIFT_COST_PER_BYTE; + + return MallocCost(cost, SExp.To(r)); + } public static Tuple BinopReduction(string opName, BigInteger initialValue, SExp args, Func opF) @@ -427,39 +574,65 @@ public static Tuple OpLogxor(SExp args) return BinopReduction("logxor", 0, args, Binop); } - // public static Tuple OpLognot(SExp args) - // { - // var (i0, l0) = ArgsAsIntList("lognot", args, 1); - // BigInteger result = ~i0; - // int cost = Costs.LOGNOT_BASE_COST + l0 * Costs.LOGNOT_COST_PER_BYTE; - // return MallocCost(cost, SExp.To(result)); - // } - - // public static Tuple OpNot(dynamic args) - // { - // List boolList = ArgsAsBoolList("not", args, 1); - // bool i0 = boolList[0]; - // bool result = !i0; - // int cost = Costs.BOOL_BASE_COST; - // return MallocCost(cost, args.To(result ? args.True : args.False)); - // } -// -// public (int, SExp) OpAny(dynamic args) -// { -// List boolList = ArgsAsBoolList("any", args, 1); -// int cost = Costs.BOOL_BASE_COST + boolList.Count * Costs.BOOL_COST_PER_ARG; -// bool result = boolList.Any(v => v); -// return (cost, args.To(result ? args.True : args.False)); -// } -// - // public (int, SExp) OpAll(dynamic args) - // { - // List boolList = ArgsAsBoolList("all", args, 1); - // int cost = Costs.BOOL_BASE_COST + boolList.Count * Costs.BOOL_COST_PER_ARG; - // bool result = boolList.All(v => v); - // return (cost, args.To(result ? args.True : args.False)); - // } + public static Tuple OpAny(SExp args) + { + var items = ArgsAsBools("any", args).ToList(); + BigInteger cost = Costs.BOOL_BASE_COST + items.Count * Costs.BOOL_COST_PER_ARG; + SExp r = SExp.False; + + foreach (var v in items) + { + var atom = v.AsAtom(); + if (atom != null && atom.Length > 0) + { + r = SExp.True; + break; + } + } + + return Tuple.Create(cost, r); + } + + + public static Tuple OpAll(SExp args) + { + var items = ArgsAsBools("all", args).ToArray(); + BigInteger cost = Costs.BOOL_BASE_COST + items.Length * Costs.BOOL_COST_PER_ARG; + var r = SExp.True; + + foreach (var v in items) + { + var atom = v.AsAtom(); + if (atom != null && atom.Length == 0 || atom is null) + { + r = SExp.False; + break; + } + } + return Tuple.Create(cost, r); + } + + public static Tuple OpLogNot(dynamic args) + { + var list = ArgsAsIntList("lognot", args, 1); + BigInteger cost = Costs.LOGNOT_BASE_COST + list[0].Item2 * Costs.LOGNOT_COST_PER_BYTE; + BigInteger s = list[0].Item1; + var inverted = -s - 1; + return MallocCost(cost, SExp.To(inverted)); + } + + public static Tuple OpNot(dynamic args) + { + var boolList = ArgsAsBoolList("not", args, 1); + SExp i0 = boolList[0]; + int cost = Costs.BOOL_BASE_COST; + + if (i0.AsAtom() is null || i0.AsAtom().SequenceEqual(Array.Empty())) + return MallocCost(cost, SExp.True); + + return MallocCost(cost, SExp.False); + } public static Tuple OpSoftfork(SExp args) { diff --git a/CLVMDotNet/src/CLVM/OpUtils.cs b/CLVMDotNet/src/CLVM/OpUtils.cs deleted file mode 100644 index 5ce9b62..0000000 --- a/CLVMDotNet/src/CLVM/OpUtils.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Reflection; - -namespace CLVMDotNet.CLVM -{ - - public static class OpUtils - { - public static Dictionary> OperatorsForDict( - Dictionary keywordToAtom, - Dictionary> opDict, - Dictionary opNameLookup = null) - { - Dictionary> result = new Dictionary>(); - - foreach (string op in keywordToAtom.Keys) - { - string opName = opNameLookup != null && opNameLookup.ContainsKey(op) - ? opNameLookup[op] - : "op_" + op; - - if (opDict.TryGetValue(opName, out Func opFunc)) - { - result[keywordToAtom[op]] = opFunc; - } - } - - return result; - } - - public static Dictionary> OperatorsForModule( - Dictionary keywordToAtom, - object mod, - Dictionary opNameLookup = null) - { - Dictionary> modDict = new Dictionary>(); - - // Get all public static methods from the module - foreach (var methodInfo in mod.GetType().GetMethods(BindingFlags.Public | BindingFlags.Static)) - { - modDict[methodInfo.Name] = (Func)Delegate.CreateDelegate( - typeof(Func), - methodInfo); - } - - return OperatorsForDict(keywordToAtom, modDict, opNameLookup); - } - } -} \ No newline at end of file diff --git a/CLVMDotNet/src/CLVM/Operators.cs b/CLVMDotNet/src/CLVM/Operators.cs index cfc1af7..84518ab 100644 --- a/CLVMDotNet/src/CLVM/Operators.cs +++ b/CLVMDotNet/src/CLVM/Operators.cs @@ -1,149 +1,255 @@ +using System.Numerics; + namespace CLVMDotNet.CLVM { - - public class OperatorDict : Dictionary>> + public static class Operator { - public byte[] QuoteAtom { get; set; } = new byte[0]; - public byte[] ApplyAtom { get; set; } = new byte[0]; - public Func> UnknownOpHandler { get; set; } = DefaultUnknownOp; - - public OperatorDict() - { - } - - public OperatorDict(Dictionary>> dict, byte[] quote = null, - byte[] apply = null, Func> unknownOpHandler = null) - : base(dict) + public static byte[] QuoteAtom { get; set; } = new byte[0]; + public static byte[] ApplyAtom { get; set; } = new byte[0]; + + /// + /// Apply Operator to an atom. + /// + /// This is not the tidiest or most efficient way of executing an operator. The + /// python version uses a dictionary to lookup the function. This will likely be changed to that + /// to make it more efficient, rather than executing 30 if statements which ultimately slows down the function. + /// + /// + /// + /// + /// + /// + public static Tuple ApplyOperator(byte[] atom, SExp args) { - if (quote != null) QuoteAtom = quote; - if (apply != null) ApplyAtom = apply; - if (unknownOpHandler != null) UnknownOpHandler = unknownOpHandler; - } + // core op codes 0x01-x08 - public Tuple Execute(byte[] op, CLVMObject arguments) - { - if (!ContainsKey(op)) + if (atom.AsSpan().SequenceEqual(new byte[] { 0x23 })) { - return UnknownOpHandler(op, arguments); + //. (#) + return CoreOps.OpDefaultUnknown(atom,args); } - else + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x01 })) { - return this[op](arguments); + //q + return CoreOps.OpDefaultUnknown(atom,args); } - } - - - public static Dictionary OP_REWRITE = new Dictionary - { - { "+", "add" }, - { "-", "subtract" }, - { "*", "multiply" }, - { "/", "div" }, - { "i", "if" }, - { "c", "cons" }, - { "f", "first" }, - { "r", "rest" }, - { "l", "listp" }, - { "x", "raise" }, - { "=", "eq" }, - { ">", "gr" }, - { ">s", "gr_bytes" } - }; - - private static IEnumerable ArgsLen(string opName, IEnumerable args) - { - foreach (var arg in args) + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x02 })) { - if (arg.Length > 1) - { - throw new EvalError($"{opName} requires int args"); - } - - yield return arg.Length; + //a + return CoreOps.OpDefaultUnknown(atom, args); } - } - public static Tuple DefaultUnknownOp(byte[] op, CLVMObject args) - { - if (op.Length == 0 || BitConverter.ToUInt16(op, 0) == 0xFFFF) + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x03 })) { - throw new EvalError("reserved operator"); + //i + return CoreOps.OpIf(args); + } + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x04 })) + { + //c + return CoreOps.OpCons(args); + } + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x05 })) + { + //f + return CoreOps.OpFirst(args); + } + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x06 })) + { + //r + return CoreOps.OpRest(args); + } + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x07 })) + { + //l + return CoreOps.OpListp(args); + } + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x08 })) + { + //x + return CoreOps.OpRaise(args); } - var costFunction = (op[0] & 0b11000000) >> 6; - - // Remove the last byte from the op array - byte[] opWithoutLastByte = op.Take(op.Length - 1).ToArray(); - // Convert the opWithoutLastByte to an integer using big-endian byte order - if (BitConverter.IsLittleEndian) + //opcodes on atoms as strings 0x09-0x0f + if (atom.AsSpan().SequenceEqual(new byte[] { 0x09 })) + { + //= + return CoreOps.OpEq(args); + } + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x0A })) + { + //>s + return MoreOps.OpGrBytes(args); + } + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x0B })) + { + //sha256 + return MoreOps.OpSha256(args); + } + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x0C })) + { + //substr + return MoreOps.OpSubstr(args); + } + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x0D })) + { + //strlen + return MoreOps.OpStrlen(args); + } + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x0E })) + { + //concat + return MoreOps.OpConcat(args); + } + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x0F })) { - Array.Reverse(opWithoutLastByte); + //. + //return MoreOps.OpSubtract(args); + throw new ArgumentException("Op Not Implemented!"); } - byte[] paddedArray = new byte[4]; - opWithoutLastByte.CopyTo(paddedArray, 0); - int costMultiplier = BitConverter.ToInt32(paddedArray, 0) + 1; - int cost; - if (costFunction == 0) + //op codes on atoms as ints + if (atom.AsSpan().SequenceEqual(new byte[] { 0x10 })) { - cost = 1; + //+ + return MoreOps.OpAdd(args); } - else if (costFunction == 1) + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x11 })) { - cost = Costs.ARITH_BASE_COST; - int argSize = 0; - foreach (var length in ArgsLen("unknown op", args as IEnumerable)) - { - argSize += length; - cost += Costs.ARITH_COST_PER_ARG; - } - - cost += argSize * Costs.ARITH_COST_PER_BYTE; + //- + return MoreOps.OpSubtract(args); + } + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x12 })) + { + //* + return MoreOps.OpDiv(args); + } + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x13 })) + { + // divide + return MoreOps.OpMultiply(args); + } + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x14 })) + { + //divmod + return MoreOps.OpDivmod(args); + } + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x15 })) + { + //> + return MoreOps.OpGr(args); } - else if (costFunction == 2) + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x16 })) { - cost = Costs.MUL_BASE_COST; - var operands = ArgsLen("unknown op", args as IEnumerable).GetEnumerator(); + //ash + return MoreOps.OpAsh(args); + } + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x17 })) + { + //lsh + return MoreOps.OpLsh(args); + } - var vs = operands.MoveNext() ? operands.Current : 0; - while (operands.MoveNext()) - { - var rs = operands.Current; - cost += Costs.MUL_COST_PER_OP; - cost += (rs + vs) * Costs.MUL_LINEAR_COST_PER_BYTE; - cost += (rs * vs) / Costs.MUL_SQUARE_COST_PER_BYTE_DIVIDER; - vs += rs; - } + // opcodes on atoms as vectors of bools 0x18-0x1c + if (atom.AsSpan().SequenceEqual(new byte[] { 0x18 })) + { + //logand + return MoreOps.OpLogand(args); + } + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x19 })) + { + //logior + return MoreOps.OpLogior(args); + } + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x1A })) + { + //logxor + return MoreOps.OpLogxor(args); + } + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x1B })) + { + //lognot + return MoreOps.OpLogNot(args); } - else if (costFunction == 3) + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x1C })) { - cost = Costs.CONCAT_BASE_COST; - int length = 0; - foreach (var arg in args as IEnumerable) - { - cost += Costs.CONCAT_COST_PER_ARG; - length += arg.Length; - } + //. + throw new ArgumentException("Op Not Implemented!"); + } + - cost += length * Costs.CONCAT_COST_PER_BYTE; + //opcodes for bls 1381 0x1d-0x1f + if (atom.AsSpan().SequenceEqual(new byte[] { 0x1D })) + { + //point_add + return MoreOps.OpPointAdd(args); + } + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x1E })) + { + //pubkey_for_exp + return MoreOps.OpPubkeyForExp(args); } - else + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x1F })) { - throw new EvalError("Invalid cost function"); + //. + throw new ArgumentException("Op Not Implemented!"); } - cost *= (int)costMultiplier; - if (cost >= Math.Pow(2, 32)) + // bool opcodes 0x20-0x23 + if (atom.AsSpan().SequenceEqual(new byte[] { 0x20 })) { - throw new EvalError("Invalid operator"); + //not + return MoreOps.OpNot(args); + } + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x21 })) + { + //any + return MoreOps.OpAny(args); + } + else if (atom.AsSpan().SequenceEqual(new byte[] { 0x22 })) + { + //all + return MoreOps.OpAll(args); + } + if (atom.AsSpan().SequenceEqual(new byte[] { 0x24 })) + { + //softfork + return MoreOps.OpSoftfork(args); } - return new Tuple(cost, null); + throw new Exception($"{BitConverter.ToString(atom).Replace("-", "")} Operator not found or is unsupported!"); } - - public static byte[] QUOTE_ATOM = Keywords.KEYWORD_TO_ATOM["q"]; - public static byte[] APPLY_ATOM = Keywords.KEYWORD_TO_ATOM["a"]; + + public static string KEYWORD_FROM_ATOM(byte atom) => atom switch + { + 0x23 => ".", + 0x02 => "a", + 0x01 => "q", + 0x03 => "i", + 0x04 => "c", + 0x05 => "f", + 0x06 => "r", + 0x07 => "l", + 0x08 => "x", + _ => throw new Exception("Invalid Atom") + }; + + public static byte KEYWORD_TO_ATOM(string keyword) => keyword switch + { + "." => 0x23, + "a" => 0x02, + "q" => 0x01, + "i" => 0x03, + "c" => 0x04, + "f" => 0x05, + "r" => 0x06, + "l" => 0x07, + "x" => 0x08, + _ => throw new Exception("Invalid Keyword") + }; } + + } \ No newline at end of file diff --git a/CLVMDotNet/src/CLVM/Program.cs b/CLVMDotNet/src/CLVM/Program.cs new file mode 100644 index 0000000..a44434d --- /dev/null +++ b/CLVMDotNet/src/CLVM/Program.cs @@ -0,0 +1,19 @@ +namespace CLVMDotNet.CLVM; + +public class Program +{ + //RunProgram + //TraversePath + //SwapOp + //ConsOp + //ApplyOp + //to_pre_eval_op + + public static byte MSBMask(byte inputByte) + { + inputByte |= (byte)(inputByte >> 1); + inputByte |= (byte)(inputByte >> 2); + inputByte |= (byte)(inputByte >> 4); + return (byte)((inputByte + 1) >> 1); + } +} \ No newline at end of file diff --git a/CLVMDotNet/src/CLVM/SExp.cs b/CLVMDotNet/src/CLVM/SExp.cs index 3dd2637..1013c92 100644 --- a/CLVMDotNet/src/CLVM/SExp.cs +++ b/CLVMDotNet/src/CLVM/SExp.cs @@ -20,7 +20,7 @@ public class SExp { public static SExp True { get; } = new SExp(new CLVMObject { Atom = new byte[] { 0x01 } }); public static SExp False { get; } = new SExp(new CLVMObject()); - public static CLVMObject NULL { get; } = null; + public static SExp NULL { get; } = new SExp(); public byte[]? Atom { get; set; } public Tuple? Pair { get; set; } @@ -50,7 +50,7 @@ public SExp() } public byte[]? AsAtom() - { + { return Atom; } @@ -110,7 +110,8 @@ public static SExp To(dynamic? v) return new SExp(v); } - var sexp = new SExp(HelperFunctions.ToSexpType(v)); + var type = HelperFunctions.ToSexpType(v); + var sexp = new SExp(type); return sexp; } @@ -184,7 +185,11 @@ public bool Equals(dynamic other) return false; } } - else if (s2.AsPair() != null || !s1.AsAtom().SequenceEqual(s2.AsAtom())) + else if (s2.AsPair() != null || s1.AsAtom() != null && s2.AsAtom() is null || s1.AsAtom() is null && s2.AsAtom() is not null) + { + return false; + } + else if (s2.AsAtom() != null && s1.AsAtom() != null && !s1.AsAtom().SequenceEqual(s2.AsAtom())) //both none null { return false; } @@ -192,42 +197,10 @@ public bool Equals(dynamic other) return true; } - catch (Exception) + catch (Exception e) { return false; } } - - public static byte[] ConvertAtomToBytes(object v) - { - if (v is byte[] byteArray) - { - return byteArray; - } - else if (v is string str) - { - return Encoding.UTF8.GetBytes(str); - } - else if (v is int intValue) - { - return BitConverter.GetBytes(intValue); - } - else if (v is null) - { - return new byte[0]; - } - else if (v is List list) - { - var result = new List(); - foreach (var item in list) - { - result.AddRange(ConvertAtomToBytes(item)); - } - - return result.ToArray(); - } - - throw new ArgumentException($"Can't cast {v.GetType()} ({v}) to bytes"); - } } } \ No newline at end of file diff --git a/CLVMDotNet/src/CLVMDotNet.csproj b/CLVMDotNet/src/CLVMDotNet.csproj index d7d963e..e1adaf4 100644 --- a/CLVMDotNet/src/CLVMDotNet.csproj +++ b/CLVMDotNet/src/CLVMDotNet.csproj @@ -7,7 +7,12 @@ - + + + + + + diff --git a/CLVMDotNet/src/Tools/IR/Clvmc.cs b/CLVMDotNet/src/Tools/IR/Clvmc.cs index a077be2..4dde14a 100644 --- a/CLVMDotNet/src/Tools/IR/Clvmc.cs +++ b/CLVMDotNet/src/Tools/IR/Clvmc.cs @@ -4,11 +4,12 @@ namespace CLVMDotNet.Tools.IR { public class Clvmc { - public static dynamic CompileCLVMText(string text, string[] searchPaths) + public static SExp CompileCLVMText(string text, string[] searchPaths) { var ir_src = IRReader.ReadIR(text); var assembled_sexp = BinUtils.AssembleFromIR(ir_src); - var input_sexp = SExp.To((assembled_sexp, Array.Empty())); + var input_sexp = SExp.To((assembled_sexp, Array.Empty())); + //run_program_for_search_paths //run_program @@ -21,6 +22,9 @@ public static void CompileCLVM(string inputPath, string outputPath, string[] sea } + + /// + /// //compile_clvm_text //compile_clvm //find_files diff --git a/CLVMDotNet/src/Tools/IR/Utils.cs b/CLVMDotNet/src/Tools/IR/Utils.cs index 31a5d75..7a5e6c2 100644 --- a/CLVMDotNet/src/Tools/IR/Utils.cs +++ b/CLVMDotNet/src/Tools/IR/Utils.cs @@ -21,7 +21,7 @@ public static BigInteger ConvertToBase256(string s) return val; } - public static string IrAsSymbol(SExp irSexp) + public static string? IrAsSymbol(SExp irSexp) { if (irSexp.Listp() && IrType(irSexp) == IRType.SYMBOL) { diff --git a/CLVMDotNet/src/Tools/Stages/Stage0.cs b/CLVMDotNet/src/Tools/Stages/Stage0.cs new file mode 100644 index 0000000..b3ff466 --- /dev/null +++ b/CLVMDotNet/src/Tools/Stages/Stage0.cs @@ -0,0 +1,25 @@ +using System.Numerics; +using CLVMDotNet.CLVM; + +namespace CLVMDotNet.Tools.Stages; + +public class Stage0 +{ + public static Tuple RunProgram(SExp program, dynamic args, BigInteger? max_cost) + { + //method eval op + //method traversePath + //method swap op + //method apply op + + BigInteger cost = 0; + + //while opstack + //pop opstack + //cost += stackFunction + if (max_cost.HasValue && cost > max_cost) + throw new EvalError("cost exceeded", SExp.To(max_cost)); + + return Tuple.Create(cost, SExp.To(1)); + } +} \ No newline at end of file diff --git a/CLVMDotNet/tests/CLVM/Casts/IntToBytes.cs b/CLVMDotNet/tests/CLVM/Casts/IntToBytes.cs index 5d27a20..cc09138 100644 --- a/CLVMDotNet/tests/CLVM/Casts/IntToBytes.cs +++ b/CLVMDotNet/tests/CLVM/Casts/IntToBytes.cs @@ -22,7 +22,23 @@ public class IntToBytes [InlineData("4096", new byte[] { 0x10, 0x00 })] [InlineData("10241024", new byte[] { 0x00, 0x9c, 0x44, 0x00 })] [InlineData("204820482048", new byte[] { 0x2F, 0xB0, 0x40, 0x88, 0x00 })] - [InlineData("20482048204820482048", new byte[] { 0x01, 0x1C, 0x3E, 0xDA, 0x52, 0xE0, 0xC0, 0x88, 0x00 } )] + [InlineData("20482048204820482048", new byte[] { 0x01, 0x1C, 0x3E, 0xDA, 0x52, 0xE0, 0xC0, 0x88, 0x00 })] + [InlineData("-1", new byte[] { 255 })] + [InlineData("-128", new byte[] { 0x80 })] + [InlineData("-129", new byte[] { 0xFF, 0x7F })] + [InlineData("-256", new byte[] { 0xFF, 0x00 })] + [InlineData("-512", new byte[] { 0xFE, 0x00 })] + [InlineData("-1024", new byte[] { 0xFC, 0x00 })] + [InlineData("-2048", new byte[] { 0xF8, 0x00 })] + [InlineData("-4096", new byte[] { 0xF0, 0x00 })] + [InlineData("-10241024", new byte[] { 0xFF, 0x63, 0xBC, 0x00 })] + [InlineData("-204820482048", new byte[] { 0xD0, 0x4F, 0xBF, 0x78, 0x00 })] + [InlineData("-20482048204820482048", new byte[] { 0xFE, 0xE3, 0xC1, 0x25, 0xAD, 0x1F, 0x3F, 0x78, 0x00 })] + //[InlineData("-204820482048204820482048204820482048204820482048204820482048", new byte[] + // { + // 0xDF, 0x5E, 0xC6, 0x63, 0xAD, 0x6F, 0xBA, 0x4E, 0x4A, 0x38, 0x39, 0x33, 0xCA, 0xD0, 0x3C, 0x22, + // 0xCE, 0x56, 0x2A, 0x58, 0xBD, 0x0C, 0x9F, 0x3F, 0x78, 0x00 + // })] public void IntToBytes_returns_expectedbytes(string numberStr, byte[] expectedBytes) { BigInteger number = BigInteger.Parse(numberStr); diff --git a/CLVMDotNet/tests/CLVM/Operators/OperatorTests.cs b/CLVMDotNet/tests/CLVM/CoreOps/OpDefaultUnknownTests.cs similarity index 70% rename from CLVMDotNet/tests/CLVM/Operators/OperatorTests.cs rename to CLVMDotNet/tests/CLVM/CoreOps/OpDefaultUnknownTests.cs index cdb90c3..7f4ec13 100644 --- a/CLVMDotNet/tests/CLVM/Operators/OperatorTests.cs +++ b/CLVMDotNet/tests/CLVM/CoreOps/OpDefaultUnknownTests.cs @@ -1,31 +1,18 @@ -using Xunit; -using x = CLVMDotNet.CLVM; -using CLVM = CLVMDotNet; +namespace CLVMDotNet.Tests.CLVM.CoreOps; -namespace CLVMDotNet.Tests.Operators +public class OpDefaultUnknownTests { - public class OperatorTests - { - // private bool handlerCalled = false; - // private Tuple UnknownHandler(byte[] name, SExp args) - // { - // handlerCalled = true; - // Assert.Equal(new byte[] { 0xff, 0xff, (byte)(0x1337 & 0xFF) }, name); - // Assert.Equal(SExp.To((Int32)1337), args); - // return Tuple.Create(42, SExp.To(new byte[] { 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72 })); - // } - - // [Fact] - // public void TestUnknownOp() - // { - // Assert.Throws(() => OPERATOR_LOOKUP(new byte[] { 0xff, 0xff, 0x1337 }, SExp.To(1337))); - // var od = new OperatorDict(OPERATOR_LOOKUP, unknownOpHandler: (name, args) => UnknownHandler(name, args)); - // var result = od(new byte[] { 0xff, 0xff, 0x1337 }, SExp.To(1337)); - // Assert.True(handlerCalled); - // Assert.Equal(42, result.Item1); - // Assert.Equal(SExp.To(new byte[] { 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72 }), result.Item2); - // } -// +// [Fact] +// public void TestUnknownOp() +// { +// Assert.Throws(() => OPERATOR_LOOKUP(new byte[] { 0xff, 0xff, 0x1337 }, SExp.To(1337))); +// var od = new OperatorDict(OPERATOR_LOOKUP, unknownOpHandler: (name, args) => UnknownHandler(name, args)); +// var result = od(new byte[] { 0xff, 0xff, 0x1337 }, SExp.To(1337)); +// Assert.True(handlerCalled); +// Assert.Equal(42, result.Item1); +// Assert.Equal(SExp.To(new byte[] { 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72 }), result.Item2); +// } +// // // [Fact] // public void TestPlus() // { @@ -67,5 +54,4 @@ public class OperatorTests // Tuple.Create(61, CLVM.SExp.NULL)); // } // } - } } \ No newline at end of file diff --git a/CLVMDotNet/tests/CLVM/CoreOps/OpEq.cs b/CLVMDotNet/tests/CLVM/CoreOps/OpEqTests.cs similarity index 79% rename from CLVMDotNet/tests/CLVM/CoreOps/OpEq.cs rename to CLVMDotNet/tests/CLVM/CoreOps/OpEqTests.cs index 7e60f91..385e26e 100644 --- a/CLVMDotNet/tests/CLVM/CoreOps/OpEq.cs +++ b/CLVMDotNet/tests/CLVM/CoreOps/OpEqTests.cs @@ -3,7 +3,7 @@ namespace CLVMDotNet.Tests.CLVM.CoreOps; [Trait("CoreOps", "OpEq")] -public class OpEq +public class OpEqTests { } \ No newline at end of file diff --git a/CLVMDotNet/tests/CLVM/CoreOps/OpIf.cs b/CLVMDotNet/tests/CLVM/CoreOps/OpFirstTests.cs similarity index 78% rename from CLVMDotNet/tests/CLVM/CoreOps/OpIf.cs rename to CLVMDotNet/tests/CLVM/CoreOps/OpFirstTests.cs index 73b7a0b..5f1fab5 100644 --- a/CLVMDotNet/tests/CLVM/CoreOps/OpIf.cs +++ b/CLVMDotNet/tests/CLVM/CoreOps/OpFirstTests.cs @@ -3,7 +3,7 @@ namespace CLVMDotNet.Tests.CLVM.CoreOps; [Trait("CoreOps", "OpFirst")] -public class OpIf +public class OpFirstTests { } \ No newline at end of file diff --git a/CLVMDotNet/tests/CLVM/CoreOps/OpFirst.cs b/CLVMDotNet/tests/CLVM/CoreOps/OpIfTests.cs similarity index 80% rename from CLVMDotNet/tests/CLVM/CoreOps/OpFirst.cs rename to CLVMDotNet/tests/CLVM/CoreOps/OpIfTests.cs index 1b3b956..8b951dd 100644 --- a/CLVMDotNet/tests/CLVM/CoreOps/OpFirst.cs +++ b/CLVMDotNet/tests/CLVM/CoreOps/OpIfTests.cs @@ -3,7 +3,7 @@ namespace CLVMDotNet.Tests.CLVM.CoreOps; [Trait("CoreOps", "OpFirst")] -public class OpFirst +public class OpIfTests { } \ No newline at end of file diff --git a/CLVMDotNet/tests/CLVM/CoreOps/OpListp.cs b/CLVMDotNet/tests/CLVM/CoreOps/OpListpTests.cs similarity index 78% rename from CLVMDotNet/tests/CLVM/CoreOps/OpListp.cs rename to CLVMDotNet/tests/CLVM/CoreOps/OpListpTests.cs index 00f8556..23709b4 100644 --- a/CLVMDotNet/tests/CLVM/CoreOps/OpListp.cs +++ b/CLVMDotNet/tests/CLVM/CoreOps/OpListpTests.cs @@ -3,7 +3,7 @@ namespace CLVMDotNet.Tests.CLVM.CoreOps; [Trait("CoreOps", "OpListp")] -public class OpListp +public class OpListpTests { } \ No newline at end of file diff --git a/CLVMDotNet/tests/CLVM/CoreOps/OpRaise.cs b/CLVMDotNet/tests/CLVM/CoreOps/OpRaiseTests.cs similarity index 78% rename from CLVMDotNet/tests/CLVM/CoreOps/OpRaise.cs rename to CLVMDotNet/tests/CLVM/CoreOps/OpRaiseTests.cs index c9dc659..af5eb7e 100644 --- a/CLVMDotNet/tests/CLVM/CoreOps/OpRaise.cs +++ b/CLVMDotNet/tests/CLVM/CoreOps/OpRaiseTests.cs @@ -3,7 +3,7 @@ namespace CLVMDotNet.Tests.CLVM.CoreOps; [Trait("CoreOps", "OpRaise")] -public class OpRaise +public class OpRaiseTests { } \ No newline at end of file diff --git a/CLVMDotNet/tests/CLVM/CoreOps/OpRest.cs b/CLVMDotNet/tests/CLVM/CoreOps/OpRestTests.cs similarity index 82% rename from CLVMDotNet/tests/CLVM/CoreOps/OpRest.cs rename to CLVMDotNet/tests/CLVM/CoreOps/OpRestTests.cs index c1348e9..4e205c4 100644 --- a/CLVMDotNet/tests/CLVM/CoreOps/OpRest.cs +++ b/CLVMDotNet/tests/CLVM/CoreOps/OpRestTests.cs @@ -3,7 +3,7 @@ namespace CLVMDotNet.Tests.CLVM.CoreOps; [Trait("CoreOps", "OpRest")] -public class OpRest +public class OpRestTests { } \ No newline at end of file diff --git a/CLVMDotNet/tests/CLVM/Operators/ApplyOperatorTests.cs b/CLVMDotNet/tests/CLVM/Operators/ApplyOperatorTests.cs new file mode 100644 index 0000000..594e125 --- /dev/null +++ b/CLVMDotNet/tests/CLVM/Operators/ApplyOperatorTests.cs @@ -0,0 +1,714 @@ +using System.Numerics; +using System.Text; +using Xunit; +using x = CLVMDotNet.CLVM; +using CLVM = CLVMDotNet; + + +namespace CLVMDotNet.Tests.CLVM.Operators +{ + [Trait("Operators", "All Operators")] + public class OperatorTests + { + #region OpAdd + + [Fact] + public void OpAdd() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x10 }, x.SExp.To(new List { 3, 4, 5 })); + var s = result; + } + + #endregion + + + [Fact] + public void OpSubtract() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x11 }, x.SExp.To(new List { 3, 1 })); + var s = result; + } + + #region OpDivide + + [Fact] + public void OpDivide() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x12 }, x.SExp.To(new List { 10, 2 })); + var s = result; + } + + [Fact] + public void OpDivideThrowsExceptionIfDividingByZero() + { + var errorMessage = + Assert.Throws(() => + x.Operator.ApplyOperator(new byte[] { 0x12 }, x.SExp.To(new List { 10, 0 })) + ); + Assert.Contains("div with 0", errorMessage.Message); + } + + [Fact()] + public void OpDivideThrowsExceptionWithNegativeOperand1() + { + var errorMessage = + Assert.Throws(() => + x.Operator.ApplyOperator(new byte[] { 0x12 }, x.SExp.To(new List { -1, 5 })) + ); + Assert.Contains("div operator with negative operands is deprecated", errorMessage.Message); + } + + #endregion + + [Fact] + public void OpMultiply() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x12 }, x.SExp.To(new List { 10, 3 })); + var s = result; + } + + [Fact] + public void OpDivMod() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x14 }, x.SExp.To(new List { 3, 5 })); + var s = result; + } + + + [Fact] + public void OpConcat() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x0E }, x.SExp.To(new List { "test", "ing" })); + var s = result; + } + + #region OpSubStr + + [Theory] + [InlineData(-1)] + [InlineData(10)] + public void OpSubstrThrowsWhenOutOfBounds(int startIndex) + { + var errorMessage = + Assert.Throws(() => + x.Operator.ApplyOperator(new byte[] { 0x0C }, x.SExp.To(new List { "kevin", startIndex })) + ); + Assert.Contains("invalid indices for substr", errorMessage.Message); + } + + [Fact] + public void OpSubstrThrowsWithTwoManyArgs() + { + var errorMessage = + Assert.Throws(() => + x.Operator.ApplyOperator(new byte[] { 0x0C }, x.SExp.To(new List { "kevin", 1, 2, 3 })) + ); + Assert.Contains("substr takes exactly 2 or 3 arguments", errorMessage.Message); + } + + [Fact] + public void OpSubstrThrowsWithTooFewArgs() + { + var errorMessage = + Assert.Throws(() => + x.Operator.ApplyOperator(new byte[] { 0x0C }, x.SExp.To(new List { "kevin" })) + ); + Assert.Contains("substr takes exactly 2 or 3 arguments", errorMessage.Message); + } + + [Theory] + [InlineData("1", "somelongstring", 4, "longstring")] + [InlineData("1", "somelongstring", 0, "somelongstring")] + [InlineData("1", "somelongstring", 13, "g")] + public void OpSubstrReturnsSubStringWithCost(string stringCost, string val, int startindex, string expectedResult) + { + BigInteger cost = BigInteger.Parse(stringCost); + var result = + x.Operator.ApplyOperator(new byte[] { 0x0C }, x.SExp.To(new List { val, startindex })); + var atom = result.Item2.AsAtom(); + string text = Encoding.UTF8.GetString(atom); + Assert.Equal(expectedResult, text); + Assert.Equal(cost, result.Item1); + } + + [Theory] + [InlineData("1", "somelongstring", 0, 2, "so")] + [InlineData("1", "somelongstring", 4, 5, "l")] + [InlineData("1", "somelongstring", 13, 14, "g")] + [InlineData("1", "somelongstring", 3, 12, "elongstri")] + public void OpSubstrReturnsSubStringOfNumberOfCharactersWithCost(string stringCost, string val, int startindex, + int endIndex, string expectedResult) + { + BigInteger cost = BigInteger.Parse(stringCost); + var result = x.Operator.ApplyOperator(new byte[] { 0x0C }, + x.SExp.To(new List { val, startindex, endIndex })); + var atom = result.Item2.AsAtom(); + string text = Encoding.UTF8.GetString(atom!); + Assert.Equal(expectedResult, text); + Assert.Equal(cost, result.Item1); + } + + #endregion + + #region OpStrLen + + [Theory] + [InlineData("somestring", 10, 193)] + [InlineData("s", 1, 184)] + [InlineData("", 0, 173)] + [InlineData("THIS IS A LONGER SENTENCE TO CALCULATE THE COST OF.", 51, 234)] + public void OpStrLen(string val, int length, int cost) + { + var result = x.Operator.ApplyOperator(new byte[] { 0x0D }, + x.SExp.To(new List { val })); + var atom = result.Item2.AsAtom(); + var actualLength = new BigInteger(atom!); + Assert.Equal(length, actualLength); + Assert.Equal(cost, result.Item1); + } + + + [Fact] + public void OpStrLenThrowsWithTooManyArgs() + { + var errorMessage = + Assert.Throws(() => + x.Operator.ApplyOperator(new byte[] { 0x0D }, + x.SExp.To(new List { "THIS", "WILL THROW AN EXCEPTION" })) + ); + Assert.Contains("strlen takes exactly 1 argument", errorMessage.Message); + } + + [Fact] + public void OpStrLenThrowsIfPair() + { + var errorMessage = + Assert.Throws(() => + x.Operator.ApplyOperator(new byte[] { 0x0D }, x.SExp.To(new List { 3, 1 })) + ); + Assert.Contains("strlen takes exactly 1 argument", errorMessage.Message); + } + + #endregion + + #region OpSHA256 + + [Fact] + public void OpSHA256() + { + var result = + x.Operator.ApplyOperator(new byte[] { 0x0B }, x.SExp.To(new List { "THIS IS A SHA256 HASH" })); + var s = result; + var atom = result.Item2.AsAtom(); + + // Assert.Equal(583, result.Item1); + Assert.True(atom.AsSpan().SequenceEqual(new byte[] + { + 0xB1, 0xBD, 0xB6, 0xD1, 0xF9, 0xA8, 0x3F, 0xA5, 0xB4, 0xFA, 0x25, 0x53, 0x34, 0xF1, 0x47, 0xC3, 0xCD, + 0x09, 0x4C, 0xE3, 0x6E, 0xC9, 0x74, 0xD5, 0xD8, 0x38, 0xF0, 0x45, 0x98, 0x08, 0x13, 0x4E + })); + } + + [Fact(Skip = "Skipping until SHA256 Throws an error")] + public void OpSHA256OnList_ThrowsError() + { + var errorMessage = + Assert.Throws(() => + x.Operator.ApplyOperator(new byte[] { 0x0B }, + x.SExp.To(new List { "SOME", "ERror" }))); + Assert.Contains("sha256 on list", errorMessage.Message); + } + + #endregion + + #region GrBytes + [Theory] + [InlineData(119, "a", "b")] + [InlineData(131, "testing", "testing")] + public void OpGrBytesReturnsFalse(int expectedCost, string val1, string val2) + { + var result = x.Operator.ApplyOperator(new byte[] { 0x0A }, x.SExp.To(new List { val1, val2 })); + var areEqual = x.SExp.False.Equals(result.Item2); + Assert.True(areEqual); + Assert.Equal(expectedCost, result.Item1); + } + + [Theory] + [InlineData(119, "b", "a")] + public void OpGrBytesReturnsTrue(int expectedCost, string val1, string val2) + { + var result = x.Operator.ApplyOperator(new byte[] { 0x0A }, x.SExp.To(new List { val1, val2 })); + var areEqual = x.SExp.True.Equals(result.Item2); + Assert.True(areEqual); + Assert.Equal(expectedCost, result.Item1); + } + + [Fact] + public void OpGrBytesThrowsWithMoreThanTwoParameters() + { + var errorMessage = + Assert.Throws(() => + x.Operator.ApplyOperator(new byte[] { 0x0A }, + x.SExp.To(new List { "val1", "val", "val3" }))); + Assert.Contains(">s takes exactly 2 arguments", errorMessage.Message); + } + + //TODO: Add test to throw when OpGrBytes is called on a pair + #endregion + + + #region OpGr + + [Theory] + [InlineData(502, "1", "2", false)] + [InlineData(502, "1", "1", false)] + public void OpGrReturnsFalse(int expectedCost, string strVal1, string strVal2, bool greaterThan) + { + BigInteger val1 = BigInteger.Parse(strVal1); + BigInteger val2 = BigInteger.Parse(strVal2); + var result = x.Operator.ApplyOperator(new byte[] { 0x15 }, x.SExp.To(new List { val1, val2 })); + var areEqual = x.SExp.False.Equals(result.Item2); + Assert.True(areEqual); + Assert.Equal(expectedCost, result.Item1); + } + + [Theory] + [InlineData(502, "4", "2", true)] + [InlineData(502, "-1", "2", true, + Skip = "-1 is 255 as an unsigned byte. and is greater than 2. Need to probably use sbyte!")] // + public void OpGrReturnsTrue(int expectedCost, string strVal1, string strVal2, bool greaterThan) + { + BigInteger val1 = BigInteger.Parse(strVal1); + BigInteger val2 = BigInteger.Parse(strVal2); + var result = x.Operator.ApplyOperator(new byte[] { 0x15 }, x.SExp.To(new List { val1, val2 })); + var areEqual = x.SExp.True.Equals(result.Item2); + Assert.True(areEqual); + Assert.Equal(expectedCost, result.Item1); + } + + [Fact] + public void OpGrThrowIfMoreThan2ArgumentsPassed() + { + var errorMessage = + Assert.Throws(() => + x.Operator.ApplyOperator(new byte[] { 0x15 }, x.SExp.To(new List { 1, 2, 3 }))); + Assert.Contains("> takes exactly 2 arguments", errorMessage.Message); + } + + [Fact] + public void OpGrThrowIfLessThan2ArgumentsPassed() + { + var errorMessage = + Assert.Throws(() => + x.Operator.ApplyOperator(new byte[] { 0x15 }, x.SExp.To(new List { 1 }))); + Assert.Contains("> takes exactly 2 arguments", errorMessage.Message); + } + + #endregion + + #region OpEq + + [Fact] + public void OpEqReturnsTrueWhenTwoStringsMatch() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x09 }, + x.SExp.To(new List { "SomeString", "SomeString" })); + var s = result; + var areEqual = x.SExp.True.Equals(result.Item2); + Assert.True(areEqual); + Assert.Equal(137, result.Item1); + } + + [Fact] + public void OpEqReturnsFalseWhenTwoStringsDoNotMatch() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x09 }, + x.SExp.To(new List { "val1", "DOTNOTMATCH" })); + var s = result; + var areEqual = x.SExp.False.Equals(result.Item2); + Assert.True(areEqual); + Assert.Equal(132, result.Item1); + } + + [Fact] + public void OpEqReturnTrueWhenTwoEmptyStringsMatchMatch() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x09 }, + x.SExp.To(new List { "", x.SExp.To(new List()) })); + var s = result; + var areEqual = x.SExp.True.Equals(result.Item2); + Assert.True(areEqual); + Assert.Equal(117, result.Item1); + } + + [Fact] + public void OpEqThrowsWhenMoreThanTwoArguments() + { + var errorMessage = + Assert.Throws(() => + x.Operator.ApplyOperator(new byte[] { 0x09 }, + x.SExp.To(new List { "1", "1", x.SExp.To("") }))); + Assert.Contains("= takes exactly 2 arguments", errorMessage.Message); + } + + + [Fact] + public void OpEqThrowsWhenLessThanTwoArguments() + { + var errorMessage = + Assert.Throws(() => + x.Operator.ApplyOperator(new byte[] { 0x09 }, + x.SExp.To(new List { "SOMESTRING" }))); + Assert.Contains("= takes exactly 2 arguments", errorMessage.Message); + } + + #endregion + + #region OpLogand + + [Fact] + public void OpLogAndInt() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x18 }, x.SExp.To(new List { 15, 244 })); + var atom = result.Item2.AsAtom(); + Assert.Equal(647, result.Item1); + Assert.True(atom!.SequenceEqual(new byte[] + { + 0x04 + })); + } + + [Fact] + public void OpLogEmptyList() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x18 }, x.SExp.To(new List { })); + var atom = result.Item2.AsAtom(); + Assert.Equal(110, result.Item1); + Assert.True(atom!.SequenceEqual(new byte[] + { + 0xFF + })); + } + + #endregion + + #region OpLogior + + [Fact] + public void OpLogior() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x19 }, x.SExp.To(new List { })); + var atom = result.Item2.AsAtom(); + Assert.Equal(100, result.Item1); + Assert.True(atom!.SequenceEqual(new byte[] + { + })); + } + + [Fact] + public void OpLogiorInt() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x19 }, x.SExp.To(new List { 35, 689 })); + var atom = result.Item2.AsAtom(); + Assert.Equal(657, result.Item1); + Assert.True(atom!.SequenceEqual(new byte[] + { + 0x02, 0xB3 + })); + } + + #endregion + + #region OpLogxor + + [Fact] + public void OpLogxor() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x1A }, x.SExp.To(new List { })); + var atom = result.Item2.AsAtom(); + Assert.Equal(100, result.Item1); + Assert.True(atom!.SequenceEqual(new byte[] + { + })); + } + + [Fact] + public void OpLogxorInt() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x1A }, + x.SExp.To(new List { 111111, 67452345657 })); + var atom = result.Item2.AsAtom(); + Assert.Equal(702, result.Item1); + Assert.True(atom!.SequenceEqual(new byte[] + { + 0x0f, 0xb4, (byte)'x', 0xaf, (byte)'>' + })); + } + + #endregion + + #region OpLogNot + + [Fact] + public void OpLogNot() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x1B }, x.SExp.To(new List { 1 })); + var atom = result.Item2.AsAtom(); + Assert.Equal(344, result.Item1); + Assert.True(atom!.SequenceEqual(new byte[] + { + 0xFE + })); + } + + [Fact] + public void OpLogNotNegativeNumbers() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x1B }, x.SExp.To(new List { -1111 })); + var atom = result.Item2.AsAtom(); + Assert.Equal(357, result.Item1); + Assert.True(atom!.SequenceEqual(new byte[] + { + 0x04, 0x56 + })); + } + + [Fact] + public void OpLogNotThrowsWithNoParameters() + { + var errorMessage = + Assert.Throws(() => + x.Operator.ApplyOperator(new byte[] { 0x1B }, + x.SExp.To(new List { }))); + Assert.Contains("lognot takes exactly 1 arguments", errorMessage.Message); + } + + #endregion + + #region OpPointAdd + + #endregion + + #region OpPubkeyForExp + + [Fact()] + public void OpPubKeyForExp() + { + var result = + x.Operator.ApplyOperator(new byte[] { 0x1E }, x.SExp.To(new List { "this is a test" })); + var atom = result.Item2.AsAtom(); + Assert.Equal(1326742, result.Item1); + Assert.True(atom!.SequenceEqual(new byte[] + { + 0xB3, 0xFD, 0x19, 0xF6, 0xB1, 0xA7, 0x59, 0xB9, 0x6E, 0x98, 0xE7, 0x45, 0x6F, 0x2F, 0x3F, 0x0C, 0x45, + 0xB0, 0xA7, 0xA1, 0x24, 0x3F, 0xF9, 0x40, 0x90, 0xFE, 0xFC, 0x51, 0x6C, 0x1B, 0x92, 0x9B, + 0x33, 0xB4, 0xF0, 0xC1, 0xC0, 0xF9, 0xBF, 0xEE, 0xD7, 0xB3, 0xC9, 0xC4, 0xFB, 0xB6, 0x00, 0x31 + })); + } + + [Fact] + public void OpPubKeyThrowsWithNoArgumentsp() + { + var errorMessage = + Assert.Throws(() => + x.Operator.ApplyOperator(new byte[] { 0x1E }, x.SExp.To(new List { }))); + Assert.Contains("pubkey_for_exp takes exactly 1 arguments", errorMessage.Message); + + } + #endregion + + #region OpAny + + [Fact] + public void OpAnyReturnsTrueIfListIsNotEmpty() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x21 }, + x.SExp.To(new List { 1 })); + var s = result; + var areEqual = x.SExp.True.Equals(result.Item2); + Assert.True(areEqual); + Assert.Equal(500, result.Item1); + } + + [Fact] + public void OpAnyReturnsTrueWithMoreThanOneArg() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x21 }, + x.SExp.To(new List { 1, 3, 4, 5 })); + var s = result; + var areEqual = x.SExp.True.Equals(result.Item2); + Assert.True(areEqual); + Assert.Equal(1400, result.Item1); + } + + [Fact] + public void OpAnyReturnsFalseWithNoArgs() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x21 }, + x.SExp.To(new List { })); + var s = result; + var areEqual = x.SExp.False.Equals(result.Item2); + Assert.True(areEqual); + Assert.Equal(200, result.Item1); + } + + #endregion + + #region OpAll + + [Fact] + public void OpAllAtomsReturnsTrue() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x22 }, x.SExp.To(new List { 1, 2, 3 })); + var s = result; + var areEqual = x.SExp.True.Equals(result.Item2); + Assert.True(areEqual); + Assert.Equal(1100, result.Item1); + } + + [Fact] + public void OpAllWithEmptyAtomsReturnsTrue() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x22 }, x.SExp.To(new List { })); + var s = result; + var areEqual = x.SExp.True.Equals(result.Item2); + Assert.True(areEqual); + Assert.Equal(200, result.Item1); + } + + [Fact] + public void OpAllWithPairReturnsFalse() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x22 }, x.SExp.To(new List { "+", 1, 2 })); + var s = result; + var areEqual = x.SExp.True.Equals(result.Item2); + Assert.True(areEqual); + Assert.Equal(1100, result.Item1); + } + + #endregion + + #region OpNot + + [Fact] + public void OpNotNoneEmptyBytes() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x20 }, x.SExp.To(new List { 0x01 })); + var s = result; + var areEqual = x.SExp.False.Equals(result.Item2); + Assert.True(areEqual); + Assert.Equal(200, result.Item1); + } + + [Fact] + public void OpNotThrowsIfEmptyBytes() + { + var errorMessage = + Assert.Throws(() => + x.Operator.ApplyOperator(new byte[] { 0x20 }, x.SExp.To(new List { }))); + Assert.Contains("not takes exactly 1 arguments", errorMessage.Message); + } + + [Fact] + public void OpNotThrowsIfMoreThanOneByte() + { + var s = x.SExp.To(new byte[] { 0x01, 0x01 }); + var t = s; + + var errorMessage = + Assert.Throws(() => + x.Operator.ApplyOperator(new byte[] { 0x20 }, x.SExp.To(new List { 0x01, 0x01 }))); + Assert.Contains("not takes exactly 1 arguments", errorMessage.Message); + } + + #endregion + + #region OpAsh + + [Fact] + public void OpAsh() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x16 }, x.SExp.To(new List { 1, 2 })); + var s = result; + Assert.Equal(new byte[] { 0x04 }, result.Item2.AsAtom()); + Assert.Equal(612, result.Item1); + } + + [Fact] + public void OpAshThrowsWhenMoreThanTwoArguments() + { + var errorMessage = + Assert.Throws(() => + x.Operator.ApplyOperator(new byte[] { 0x16 }, + x.SExp.To(new List { 1, 2, 4 }))); + Assert.Contains("ash takes exactly 2 arguments", errorMessage.Message); + } + + [Fact] + public void OpAshThrowsWhenLessThanTwoArguments() + { + var errorMessage = + Assert.Throws(() => + x.Operator.ApplyOperator(new byte[] { 0x16 }, + x.SExp.To(new List { 1, 2, 4 }))); + Assert.Contains("ash takes exactly 2 arguments", errorMessage.Message); + } + + #endregion + + #region OpLsh + + [Fact()] + public void OpLsh() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x17 }, x.SExp.To(new List { 1, 45 })); + var s = result; + Assert.Equal(new byte[] { 32, 0, 0, 0, 0, 0 }, result.Item2.AsAtom()); + Assert.Equal(358, result.Item1); + } + + #endregion + + #region OpDefaultUnknown + + [Fact] + public void UnsupportedOpThrowsException() + { + var errorMessage = + Assert.Throws(() => + x.Operator.ApplyOperator(new byte[] { 0x3a }, + x.SExp.NULL)); + Assert.Contains("3A Operator not found or is unsupported!", errorMessage.Message); + } + + [Fact] + public void OpDefaultUnknownAtom() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x02 }, x.SExp.To(new List { 1, 2 })); + var s = result; + Assert.Null(result.Item2.AsAtom()); + Assert.Equal(1, result.Item1); + } + + [Fact] + public void OpDefaultUnknownQuote() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x01 }, x.SExp.To(new List { 1, 2 })); + var s = result; + Assert.Null(result.Item2.AsAtom()); + Assert.Equal(1, result.Item1); + } + + [Fact] + public void OpDefaultUnknownDot() + { + var result = x.Operator.ApplyOperator(new byte[] { 0x23 }, x.SExp.To(new List { 1, 2 })); + var s = result; + Assert.Null(result.Item2.AsAtom()); + Assert.Equal(1, result.Item1); + } + + //TODO: Determine how apply operator can be called to call OpdefaultUnknown with op other than 0x01,0x02,0x23 + #endregion + } +} \ No newline at end of file diff --git a/CLVMDotNet/tests/CLVM/Operators/KeywordFromAtomTests.cs b/CLVMDotNet/tests/CLVM/Operators/KeywordFromAtomTests.cs new file mode 100644 index 0000000..f45393e --- /dev/null +++ b/CLVMDotNet/tests/CLVM/Operators/KeywordFromAtomTests.cs @@ -0,0 +1,32 @@ +using Xunit; +using x = CLVMDotNet.CLVM.Operator; + +namespace CLVMDotNet.Tests.CLVM.Operators; + +public class KeywordFromAtomTests +{ + [Theory] + [InlineData(0x23, ".")] + [InlineData(0x02, "a")] + [InlineData(0x01, "q")] + [InlineData(0x03, "i")] + [InlineData(0x04, "c")] + [InlineData(0x05, "f")] + [InlineData(0x06, "r")] + [InlineData(0x07, "l")] + [InlineData(0x08, "x")] + public void KeywordToAtom_Returns_correct_byte(byte atom, string expectedKeyword) + { + var result = x.KEYWORD_FROM_ATOM(atom); + Assert.Equal(result, expectedKeyword); + } + + [Fact] + public void UnknownAtom_ThrowsError() + { + var errorMessage = + Assert.Throws(() => + x.KEYWORD_FROM_ATOM(0xaa)); + Assert.Contains("Invalid Atom", errorMessage.Message); + } +} \ No newline at end of file diff --git a/CLVMDotNet/tests/CLVM/Operators/KeywordToAtomTests.cs b/CLVMDotNet/tests/CLVM/Operators/KeywordToAtomTests.cs new file mode 100644 index 0000000..e4e79c8 --- /dev/null +++ b/CLVMDotNet/tests/CLVM/Operators/KeywordToAtomTests.cs @@ -0,0 +1,33 @@ +using Xunit; +using clvm = CLVMDotNet.CLVM.Operator; + +namespace CLVMDotNet.Tests.CLVM.Operators; + +public class KeywordToAtomTests +{ + [Theory] + [InlineData(".", 0x23)] + [InlineData("a", 0x02)] + [InlineData("q", 0x01)] + [InlineData("i", 0x03)] + [InlineData("c", 0x04)] + [InlineData("f", 0x05)] + [InlineData("r", 0x06)] + [InlineData("l", 0x07)] + [InlineData("x", 0x08)] + public void KeywordToAtom_Returns_correct_byte(string keyword, byte expectedByte) + { + var result = clvm.KEYWORD_TO_ATOM(keyword); + Assert.Equal(result, expectedByte); + } + + [Fact] + public void UnknownKeyword_ThrowsError() + { + var errorMessage = + Assert.Throws(() => + clvm.KEYWORD_TO_ATOM("SomeInvalidKeyword")); + Assert.Contains("Invalid Keyword", errorMessage.Message); + } +} + diff --git a/CLVMDotNet/tests/CLVM/Operators/OperatorDictTests.cs b/CLVMDotNet/tests/CLVM/Operators/OperatorDictTests.cs deleted file mode 100644 index 0a5afad..0000000 --- a/CLVMDotNet/tests/CLVM/Operators/OperatorDictTests.cs +++ /dev/null @@ -1,31 +0,0 @@ -// using Xunit; -// -// namespace clvm_dotnet.tests; -// -// public class operatordicttests -// { -// [Fact] -// public void TestOperatorDictConstructor() -// { -// // Constructing should fail if quote or apply are not specified, -// // either by object property or by keyword argument. -// // Note that they cannot be specified in the operator dictionary itself. -// var d = new Dictionary { { 1, "hello" }, { 2, "goodbye" } }; -// -// Assert.Throws(() => new OperatorDict(d)); -// Assert.Throws(() => new OperatorDict(d, apply: 1)); -// Assert.Throws(() => new OperatorDict(d, quote: 1)); -// -// var o = new OperatorDict(d, apply: 1, quote: 2); -// -// // Why does the constructed Operator dict contain entries for "apply":1 and "quote":2 ? -// // assert d == o -// Assert.Equal(1, o.ApplyAtom); -// Assert.Equal(2, o.QuoteAtom); -// -// // Test construction from an already existing OperatorDict -// var o2 = new OperatorDict(o); -// Assert.Equal(1, o2.ApplyAtom); -// Assert.Equal(2, o2.QuoteAtom); -// } -// } \ No newline at end of file diff --git a/CLVMDotNet/tests/CLVM/SExp/AsBin.cs b/CLVMDotNet/tests/CLVM/SExp/AsBin.cs index 353b5f8..83bd88a 100644 --- a/CLVMDotNet/tests/CLVM/SExp/AsBin.cs +++ b/CLVMDotNet/tests/CLVM/SExp/AsBin.cs @@ -14,9 +14,15 @@ public class AsBin // [InlineData(new byte[] { 8, 9 }, new byte[] {512,512,512})] // [InlineData(new byte[] { 8, 9 }, new byte[] {1024,1024,1024,1024})] // [InlineData(new byte[] { 8, 9 }, new List {2048,248,2048,2048,2048})] - public void sexp_AsBinIsCorrectOrder(byte[] expected, dynamic sexp_list) + public void sexp_AsBinIsCorrectOrder(byte[] expected, int[] sexp_list) { - var v = x.SExp.To(sexp_list); + var lst = new List(); + foreach (var item in sexp_list) + { + lst.Add(item); + } + + var v = x.SExp.To(lst); var bytes = v.AsBin(); Assert.Equal(expected, bytes); } diff --git a/CLVMDotNet/tests/CLVM/SExp/Equals.cs b/CLVMDotNet/tests/CLVM/SExp/Equals.cs index 0afe6a4..2fa88da 100644 --- a/CLVMDotNet/tests/CLVM/SExp/Equals.cs +++ b/CLVMDotNet/tests/CLVM/SExp/Equals.cs @@ -9,8 +9,8 @@ public class Equals [Fact] public void Two_identical_sexp_are_equal() { - var s = x.SExp.To(new dynamic[] { "+", 1, 2 }); - var t = x.SExp.To(new dynamic[] { "+", 1, 2 }); + var s = x.SExp.To(new List { "+", 1, 2 }); + var t = x.SExp.To(new List { "+", 1, 2 }); var isEqual = t.Equals(s); Assert.True(isEqual); } diff --git a/CLVMDotNet/tests/CLVM/SExp/To.cs b/CLVMDotNet/tests/CLVM/SExp/To.cs index 2fb7976..3c7ad0a 100644 --- a/CLVMDotNet/tests/CLVM/SExp/To.cs +++ b/CLVMDotNet/tests/CLVM/SExp/To.cs @@ -11,7 +11,7 @@ public class To [Fact] public void builds_correct_tree() { - var s = clvm.SExp.To(new dynamic[] { "+", 1, 2 }); + var s = clvm.SExp.To(new List { "+", 1, 2 }); var t = s; var tree = Common.PrintTree(t); Assert.Equal("(43 (1 (2 () )))", tree); @@ -21,14 +21,31 @@ public void builds_correct_tree() public void test_case_1() { var sexp = clvm.SExp.To(Encoding.UTF8.GetBytes("foo")); - var t1 = clvm.SExp.To(new dynamic[] { 1, sexp }); + var t1 = clvm.SExp.To(new List { 1, sexp }); Common.ValidateSExp(t1); } + [Fact] + public void NumberAtomIsSet() + { + var a = clvm.SExp.To(1); + var atom = a.AsAtom(); + Assert.Equal(new byte[] { 0x01}, atom); + } + + [Fact] + public void StringAtomIsSet() + { + var a = clvm.SExp.To("somestring"); + var atom = a.AsAtom(); + Assert.Equal(new byte[] { 115, 111, 109, 101, 115,116, 114, 105, 110, 103}, atom); + } + + [Fact] public void TestListConversions() { - var a = clvm.SExp.To(new object[] { 1, 2, 3 }); + var a = clvm.SExp.To(new List { 1, 2, 3 }); string expectedOutput = "(1 (2 (3 () )))"; string result = Common.PrintTree(a); Assert.Equal(expectedOutput, result); diff --git a/CLVMDotNet/tests/CLVM/Serialize/CommonTests.cs b/CLVMDotNet/tests/CLVM/Serialize/CommonTests.cs index 38bff72..e9b8046 100644 --- a/CLVMDotNet/tests/CLVM/Serialize/CommonTests.cs +++ b/CLVMDotNet/tests/CLVM/Serialize/CommonTests.cs @@ -6,7 +6,7 @@ namespace CLVMDotNet.Tests.Serialize [Trait("Serialize", "Common")] public class CommonTests { - public static void CheckSerde(int[] s) + public static void CheckSerde(List s) { var v = x.SExp.To(s); var b = v.AsBin(); @@ -26,7 +26,7 @@ public static void CheckSerde(int[] s) [Fact] public void EmptyString() { - CheckSerde(Array.Empty()); + CheckSerde(new List()); } // [Fact] diff --git a/CLVMDotNet/tests/Common.cs b/CLVMDotNet/tests/Common.cs index d5c9d91..a672452 100644 --- a/CLVMDotNet/tests/Common.cs +++ b/CLVMDotNet/tests/Common.cs @@ -46,7 +46,7 @@ public static string PrintTree(clvm.SExp tree) var ret = "("; var pairs = tree.AsPair(); - var list = new List() { pairs.Item1, pairs.Item2 }; + var list = new List() { pairs!.Item1, pairs.Item2 }; if (pairs != null) { foreach (var i in list) diff --git a/CLVMDotNet/tests/Tools/Clvmc/CompileCLVMText.cs b/CLVMDotNet/tests/Tools/Clvmc/CompileCLVMText.cs new file mode 100644 index 0000000..706f7c8 --- /dev/null +++ b/CLVMDotNet/tests/Tools/Clvmc/CompileCLVMText.cs @@ -0,0 +1,12 @@ +using Xunit; + +namespace CLVMDotNet.Tests.Tools.Clvmc; + +public class CompileCLVMText +{ + [Fact] + public void RunBasicProgram() + { + var result = CLVMDotNet.Tools.IR.Clvmc.CompileCLVMText("(/ 10 2)", Array.Empty()); + } +} \ No newline at end of file