diff --git a/encoding/ccf/ccf_test.go b/encoding/ccf/ccf_test.go index 040ce352cd..d53cface17 100644 --- a/encoding/ccf/ccf_test.go +++ b/encoding/ccf/ccf_test.go @@ -2469,6 +2469,98 @@ func TestEncodeWord64(t *testing.T) { }...) } +func TestEncodeWord128(t *testing.T) { + + t.Parallel() + + testAllEncodeAndDecode(t, []encodeTest{ + { + name: "Zero", + val: cadence.NewWord128(0), + expected: []byte{ + // language=json, format=json-cdc + // {"type":"Word128","value":"0"} + // + // language=edn, format=ccf + // 130([137(52), 0]) + // + // language=cbor, format=ccf + // tag + 0xd8, ccf.CBORTagTypeAndValue, + // array, 2 items follow + 0x82, + // tag + 0xd8, ccf.CBORTagSimpleType, + // Word128 type ID (52) + 0x18, 0x34, + // tag (big num) + 0xc2, + // bytes, 0 bytes follow + 0x40, + }, + }, + { + name: "Max", + val: cadence.Word128{Value: sema.Word128TypeMaxIntBig}, + expected: []byte{ + // language=json, format=json-cdc + // {"type":"Word128","value":"340282366920938463463374607431768211455"} + // + // language=edn, format=ccf + // 130([137(52), 340282366920938463463374607431768211455]) + // + // language=cbor, format=ccf + // tag + 0xd8, ccf.CBORTagTypeAndValue, + // array, 2 items follow + 0x82, + // tag + 0xd8, ccf.CBORTagSimpleType, + // Word128 type ID (52) + 0x18, 0x34, + // tag (big num) + 0xc2, + // bytes, 16 bytes follow + 0x50, + // 340282366920938463463374607431768211455 + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + }, + }, + }...) +} + +func TestDecodeWord128Invalid(t *testing.T) { + t.Parallel() + + _, err := ccf.Decode(nil, []byte{ + // language=json, format=json-cdc + // {"type":"Word128","value":"0"} + // + // language=edn, format=ccf + // 130([137(52), 0]) + // + // language=cbor, format=ccf + // tag + 0xd8, ccf.CBORTagTypeAndValue, + // array, 2 items follow + 0x82, + // tag + 0xd8, ccf.CBORTagSimpleType, + // Word128 type ID (52) + 0x18, 0x34, + // Invalid type + 0xd7, + // bytes, 16 bytes follow + 0x50, + // 340282366920938463463374607431768211455 + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + }) + require.Error(t, err) + assert.Equal(t, "ccf: failed to decode: failed to decode Word128: cbor: cannot decode CBOR tag type to big.Int", err.Error()) +} + func TestEncodeFix64(t *testing.T) { t.Parallel() @@ -7589,6 +7681,7 @@ func TestEncodeSimpleTypes(t *testing.T) { {cadence.Word16Type{}, ccf.TypeWord16}, {cadence.Word32Type{}, ccf.TypeWord32}, {cadence.Word64Type{}, ccf.TypeWord64}, + {cadence.Word128Type{}, ccf.TypeWord128}, {cadence.Fix64Type{}, ccf.TypeFix64}, {cadence.UFix64Type{}, ccf.TypeUFix64}, {cadence.BlockType{}, ccf.TypeBlock}, diff --git a/encoding/ccf/decode.go b/encoding/ccf/decode.go index 274e9cfaa4..cdf39925bf 100644 --- a/encoding/ccf/decode.go +++ b/encoding/ccf/decode.go @@ -265,6 +265,7 @@ func (d *Decoder) decodeTypeAndValue(types *cadenceTypeByCCFTypeID) (cadence.Val // / word16-value // / word32-value // / word64-value +// / word128-value // / fix64-value // / ufix64-value func (d *Decoder) decodeValue(t cadence.Type, types *cadenceTypeByCCFTypeID) (cadence.Value, error) { @@ -352,6 +353,9 @@ func (d *Decoder) decodeValue(t cadence.Type, types *cadenceTypeByCCFTypeID) (ca case cadence.Word64Type: return d.decodeWord64() + case cadence.Word128Type: + return d.decodeWord128() + case cadence.Fix64Type: return d.decodeFix64() @@ -798,6 +802,23 @@ func (d *Decoder) decodeWord64() (cadence.Value, error) { return cadence.NewMeteredWord64(d.gauge, i), nil } +// decodeWord128 decodes word128-value as +// language=CDDL +// word128-value = bigint .ge 0 +func (d *Decoder) decodeWord128() (cadence.Value, error) { + // NewMeteredWord128FromBig checks if decoded big.Int is positive. + return cadence.NewMeteredWord128FromBig( + d.gauge, + func() *big.Int { + bigInt, err := d.dec.DecodeBigInt() + if err != nil { + panic(fmt.Errorf("failed to decode Word128: %s", err)) + } + return bigInt + }, + ) +} + // decodeFix64 decodes fix64-value as // language=CDDL // fix64-value = (int .ge -9223372036854775808) .le 9223372036854775807 diff --git a/encoding/ccf/decode_type.go b/encoding/ccf/decode_type.go index 6ab8e674bf..75cfdcf921 100644 --- a/encoding/ccf/decode_type.go +++ b/encoding/ccf/decode_type.go @@ -176,6 +176,9 @@ func (d *Decoder) decodeSimpleTypeID() (cadence.Type, error) { case TypeWord64: return cadence.TheWord64Type, nil + case TypeWord128: + return cadence.TheWord128Type, nil + case TypeFix64: return cadence.TheFix64Type, nil diff --git a/encoding/ccf/encode.go b/encoding/ccf/encode.go index 5b94d3da57..67767f29e6 100644 --- a/encoding/ccf/encode.go +++ b/encoding/ccf/encode.go @@ -322,6 +322,7 @@ func (e *Encoder) encodeTypeDefs(types []cadence.Type, tids ccfTypeIDByCadenceTy // / word16-value // / word32-value // / word64-value +// / word128-value // / fix64-value // / ufix64-value // @@ -447,6 +448,9 @@ func (e *Encoder) encodeValue( case cadence.Word64: return e.encodeWord64(v) + case cadence.Word128: + return e.encodeWord128(v) + case cadence.Fix64: return e.encodeFix64(v) @@ -690,6 +694,13 @@ func (e *Encoder) encodeWord64(v cadence.Word64) error { return e.enc.EncodeUint64(uint64(v)) } +// encodeWord128 encodes cadence.Word128 as +// language=CDDL +// word128-value = uint .ge 0 +func (e *Encoder) encodeWord128(v cadence.Word128) error { + return e.enc.EncodeBigInt(v.Big()) +} + // encodeFix64 encodes cadence.Fix64 as // language=CDDL // fix64-value = (int .ge -9223372036854775808) .le 9223372036854775807 diff --git a/encoding/ccf/simple_type_utils.go b/encoding/ccf/simple_type_utils.go index 66531e94b3..0e0f498c19 100644 --- a/encoding/ccf/simple_type_utils.go +++ b/encoding/ccf/simple_type_utils.go @@ -81,6 +81,7 @@ const ( // Cadence simple type IDs TypeBytes TypeVoid TypeFunction + TypeWord128 ) // NOTE: cadence.FunctionType isn't included in simpleTypeIDByType @@ -194,6 +195,9 @@ func simpleTypeIDByType(typ cadence.Type) (uint64, bool) { case cadence.Word64Type: return TypeWord64, true + case cadence.Word128Type: + return TypeWord128, true + case cadence.Fix64Type: return TypeFix64, true diff --git a/encoding/ccf/traverse_value.go b/encoding/ccf/traverse_value.go index bc8e3ec500..c75bc40561 100644 --- a/encoding/ccf/traverse_value.go +++ b/encoding/ccf/traverse_value.go @@ -201,6 +201,7 @@ func (ct *compositeTypes) traverseType(typ cadence.Type) (checkRuntimeType bool) cadence.Word16Type, cadence.Word32Type, cadence.Word64Type, + cadence.Word128Type, cadence.Fix64Type, cadence.UFix64Type, cadence.PathType, diff --git a/encoding/json/decode.go b/encoding/json/decode.go index 56c3659958..ca96867a7c 100644 --- a/encoding/json/decode.go +++ b/encoding/json/decode.go @@ -201,6 +201,8 @@ func (d *Decoder) decodeJSON(v any) cadence.Value { return d.decodeWord32(valueJSON) case word64TypeStr: return d.decodeWord64(valueJSON) + case word128TypeStr: + return d.decodeWord128(valueJSON) case fix64TypeStr: return d.decodeFix64(valueJSON) case ufix64TypeStr: @@ -565,6 +567,24 @@ func (d *Decoder) decodeWord64(valueJSON any) cadence.Word64 { return cadence.NewMeteredWord64(d.gauge, i) } +func (d *Decoder) decodeWord128(valueJSON any) cadence.Word128 { + value, err := cadence.NewMeteredWord128FromBig( + d.gauge, + func() *big.Int { + bigInt := d.decodeBigInt(valueJSON) + if bigInt == nil { + // TODO: propagate toString error from decodeBigInt + panic(errors.NewDefaultUserError("invalid Word128: %s", valueJSON)) + } + return bigInt + }, + ) + if err != nil { + panic(errors.NewDefaultUserError("invalid Word128: %w", err)) + } + return value +} + func (d *Decoder) decodeFix64(valueJSON any) cadence.Fix64 { v, err := cadence.NewMeteredFix64(d.gauge, func() (string, error) { return toString(valueJSON), nil @@ -1213,6 +1233,8 @@ func (d *Decoder) decodeType(valueJSON any, results typeDecodingResults) cadence return cadence.TheWord32Type case "Word64": return cadence.TheWord64Type + case "Word128": + return cadence.TheWord128Type case "Fix64": return cadence.TheFix64Type case "UFix64": diff --git a/encoding/json/encode.go b/encoding/json/encode.go index bd2bc9e4ec..759dfc8955 100644 --- a/encoding/json/encode.go +++ b/encoding/json/encode.go @@ -245,6 +245,7 @@ const ( word16TypeStr = "Word16" word32TypeStr = "Word32" word64TypeStr = "Word64" + word128TypeStr = "Word128" fix64TypeStr = "Fix64" ufix64TypeStr = "UFix64" arrayTypeStr = "Array" @@ -314,6 +315,8 @@ func Prepare(v cadence.Value) jsonValue { return prepareWord32(v) case cadence.Word64: return prepareWord64(v) + case cadence.Word128: + return prepareWord128(v) case cadence.Fix64: return prepareFix64(v) case cadence.UFix64: @@ -522,6 +525,13 @@ func prepareWord64(v cadence.Word64) jsonValue { } } +func prepareWord128(v cadence.Word128) jsonValue { + return jsonValueObject{ + Type: word128TypeStr, + Value: encodeBig(v.Big()), + } +} + func prepareFix64(v cadence.Fix64) jsonValue { return jsonValueObject{ Type: fix64TypeStr, @@ -758,6 +768,7 @@ func prepareType(typ cadence.Type, results typePreparationResults) jsonValue { cadence.Word16Type, cadence.Word32Type, cadence.Word64Type, + cadence.Word128Type, cadence.Fix64Type, cadence.UFix64Type, cadence.BlockType, diff --git a/encoding/json/encoding_test.go b/encoding/json/encoding_test.go index 8e24e6f7d2..cbcb40f13f 100644 --- a/encoding/json/encoding_test.go +++ b/encoding/json/encoding_test.go @@ -617,6 +617,26 @@ func TestEncodeWord64(t *testing.T) { }...) } +func TestEncodeWord128(t *testing.T) { + + t.Parallel() + + testAllEncodeAndDecode(t, []encodeTest{ + { + "Zero", + cadence.NewWord128(0), + // language=json + `{"type":"Word128","value":"0"}`, + }, + { + "Max", + cadence.Word128{Value: sema.Word128TypeMaxIntBig}, + // language=json + `{"type":"Word128","value":"340282366920938463463374607431768211455"}`, + }, + }...) +} + func TestEncodeFix64(t *testing.T) { t.Parallel() @@ -1736,6 +1756,7 @@ func TestEncodeSimpleTypes(t *testing.T) { cadence.Word16Type{}, cadence.Word32Type{}, cadence.Word64Type{}, + cadence.Word128Type{}, cadence.Fix64Type{}, cadence.UFix64Type{}, cadence.BlockType{}, diff --git a/runtime/convertTypes.go b/runtime/convertTypes.go index c2a4c830aa..5bd6f03302 100644 --- a/runtime/convertTypes.go +++ b/runtime/convertTypes.go @@ -123,6 +123,8 @@ func ExportMeteredType( return cadence.TheWord32Type case sema.Word64Type: return cadence.TheWord64Type + case sema.Word128Type: + return cadence.TheWord128Type case sema.Fix64Type: return cadence.TheFix64Type case sema.UFix64Type: @@ -602,6 +604,8 @@ func ImportType(memoryGauge common.MemoryGauge, t cadence.Type) interpreter.Stat return interpreter.NewPrimitiveStaticType(memoryGauge, interpreter.PrimitiveStaticTypeWord32) case cadence.Word64Type: return interpreter.NewPrimitiveStaticType(memoryGauge, interpreter.PrimitiveStaticTypeWord64) + case cadence.Word128Type: + return interpreter.NewPrimitiveStaticType(memoryGauge, interpreter.PrimitiveStaticTypeWord128) case cadence.Fix64Type: return interpreter.NewPrimitiveStaticType(memoryGauge, interpreter.PrimitiveStaticTypeFix64) case cadence.UFix64Type: diff --git a/runtime/convertValues.go b/runtime/convertValues.go index 2e1325104e..6d3fbd3ca4 100644 --- a/runtime/convertValues.go +++ b/runtime/convertValues.go @@ -184,6 +184,13 @@ func exportValueWithInterpreter( return cadence.NewMeteredWord32(inter, uint32(v)), nil case interpreter.Word64Value: return cadence.NewMeteredWord64(inter, uint64(v)), nil + case interpreter.Word128Value: + return cadence.NewMeteredWord128FromBig( + inter, + func() *big.Int { + return v.ToBigInt(inter) + }, + ) case interpreter.Fix64Value: return cadence.Fix64(v), nil case interpreter.UFix64Value: @@ -793,6 +800,8 @@ func (i valueImporter) importValue(value cadence.Value, expectedType sema.Type) return i.importWord32(v), nil case cadence.Word64: return i.importWord64(v), nil + case cadence.Word128: + return i.importWord128(v), nil case cadence.Fix64: return i.importFix64(v), nil case cadence.UFix64: @@ -1033,6 +1042,15 @@ func (i valueImporter) importWord64(v cadence.Word64) interpreter.Word64Value { ) } +func (i valueImporter) importWord128(v cadence.Word128) interpreter.Word128Value { + return interpreter.NewWord128ValueFromBigInt( + i.inter, + func() *big.Int { + return v.Value + }, + ) +} + func (i valueImporter) importFix64(v cadence.Fix64) interpreter.Fix64Value { return interpreter.NewFix64Value( i.inter, diff --git a/runtime/convertValues_test.go b/runtime/convertValues_test.go index 0c74bd43ae..3513d2763e 100644 --- a/runtime/convertValues_test.go +++ b/runtime/convertValues_test.go @@ -366,6 +366,11 @@ func TestExportValue(t *testing.T) { value: interpreter.NewUnmeteredWord64Value(42), expected: cadence.NewWord64(42), }, + { + label: "Word128", + value: interpreter.NewUnmeteredWord128ValueFromUint64(42), + expected: cadence.NewWord128(42), + }, { label: "Fix64", value: interpreter.NewUnmeteredFix64Value(-123000000), @@ -800,6 +805,11 @@ func TestImportValue(t *testing.T) { value: cadence.NewWord64(42), expected: interpreter.NewUnmeteredWord64Value(42), }, + { + label: "Word128", + value: cadence.NewWord128(42), + expected: interpreter.NewUnmeteredWord128ValueFromUint64(42), + }, { label: "Fix64", value: cadence.Fix64(-123000000), @@ -1070,6 +1080,11 @@ func TestImportRuntimeType(t *testing.T) { actual: cadence.Word64Type{}, expected: interpreter.PrimitiveStaticTypeWord64, }, + { + label: "Word128", + actual: cadence.Word128Type{}, + expected: interpreter.PrimitiveStaticTypeWord128, + }, { label: "Fix64", actual: cadence.Fix64Type{}, @@ -2764,6 +2779,11 @@ func TestRuntimeArgumentPassing(t *testing.T) { typeSignature: "Word64", exportedValue: cadence.NewWord64(42), }, + { + label: "Word128", + typeSignature: "Word128", + exportedValue: cadence.NewWord128(42), + }, { label: "Fix64", typeSignature: "Fix64", diff --git a/runtime/imported_values_memory_metering_test.go b/runtime/imported_values_memory_metering_test.go index 3fe52be2ac..8debfd7160 100644 --- a/runtime/imported_values_memory_metering_test.go +++ b/runtime/imported_values_memory_metering_test.go @@ -322,6 +322,18 @@ func TestImportedValueMemoryMetering(t *testing.T) { assert.Equal(t, uint64(8), meter[common.MemoryKindNumberValue]) }) + t.Run("Word128", func(t *testing.T) { + t.Parallel() + + script := []byte(` + pub fun main(x: Word128) {} + `) + + meter := make(map[common.MemoryKind]uint64) + executeScript(t, script, meter, cadence.NewWord128(2)) + assert.Equal(t, uint64(16), meter[common.MemoryKindBigInt]) + }) + t.Run("Fix64", func(t *testing.T) { t.Parallel() diff --git a/runtime/interpreter/decode.go b/runtime/interpreter/decode.go index 6ae72f1789..793560635a 100644 --- a/runtime/interpreter/decode.go +++ b/runtime/interpreter/decode.go @@ -288,6 +288,9 @@ func (d StorableDecoder) decodeStorable() (atree.Storable, error) { case CBORTagWord64Value: storable, err = d.decodeWord64() + case CBORTagWord128Value: + storable, err = d.decodeWord128() + // Fix* case CBORTagFix64Value: @@ -734,6 +737,28 @@ func (d StorableDecoder) decodeWord64() (Word64Value, error) { return NewUnmeteredWord64Value(value), nil } +func (d StorableDecoder) decodeWord128() (Word128Value, error) { + bigInt, err := d.decodeBigInt() + if err != nil { + if e, ok := err.(*cbor.WrongTypeError); ok { + return Word128Value{}, errors.NewUnexpectedError("invalid Word128 encoding: %s", e.ActualType.String()) + } + return Word128Value{}, err + } + + if bigInt.Sign() < 0 { + return Word128Value{}, errors.NewUnexpectedError("invalid Word128: got %s, expected positive", bigInt) + } + + max := sema.Word128TypeMaxIntBig + if bigInt.Cmp(max) > 0 { + return Word128Value{}, errors.NewUnexpectedError("invalid Word128: got %s, expected max %s", bigInt, max) + } + + // NOTE: already metered by `decodeBigInt` + return NewUnmeteredWord128ValueFromBigInt(bigInt), nil +} + func (d StorableDecoder) decodeFix64() (Fix64Value, error) { value, err := decodeInt64(d) if err != nil { diff --git a/runtime/interpreter/encode.go b/runtime/interpreter/encode.go index 5e1cdabf99..6cee683e1d 100644 --- a/runtime/interpreter/encode.go +++ b/runtime/interpreter/encode.go @@ -158,7 +158,7 @@ const ( CBORTagWord16Value CBORTagWord32Value CBORTagWord64Value - _ // future: Word128 + CBORTagWord128Value _ // future: Word256 _ @@ -613,6 +613,23 @@ func (v Word64Value) Encode(e *atree.Encoder) error { return e.CBOR.EncodeUint64(uint64(v)) } +// Encode encodes Word128Value as +// +// cbor.Tag{ +// Number: CBORTagWord128Value, +// Content: *big.Int(v.BigInt), +// } +func (v Word128Value) Encode(e *atree.Encoder) error { + err := e.CBOR.EncodeRawBytes([]byte{ + // tag number + 0xd8, CBORTagWord128Value, + }) + if err != nil { + return err + } + return e.CBOR.EncodeBigInt(v.BigInt) +} + // Encode encodes Fix64Value as // // cbor.Tag{ diff --git a/runtime/interpreter/encoding_test.go b/runtime/interpreter/encoding_test.go index 3157c96c4c..6b23df5052 100644 --- a/runtime/interpreter/encoding_test.go +++ b/runtime/interpreter/encoding_test.go @@ -2494,6 +2494,126 @@ func TestEncodeDecodeWord64Value(t *testing.T) { }) } +func TestEncodeDecodeWord128Value(t *testing.T) { + + t.Parallel() + + t.Run("zero", func(t *testing.T) { + t.Parallel() + + testEncodeDecode(t, + encodeDecodeTest{ + value: NewUnmeteredWord128ValueFromUint64(0), + encoded: []byte{ + 0xd8, CBORTagWord128Value, + // positive bignum + 0xc2, + // byte string, length 0 + 0x40, + }, + }, + ) + }) + + t.Run("positive", func(t *testing.T) { + t.Parallel() + + testEncodeDecode(t, + encodeDecodeTest{ + value: NewUnmeteredWord128ValueFromUint64(42), + encoded: []byte{ + 0xd8, CBORTagWord128Value, + // positive bignum + 0xc2, + // byte string, length 1 + 0x41, + 0x2a, + }, + }, + ) + }) + + t.Run("max", func(t *testing.T) { + t.Parallel() + + testEncodeDecode(t, + encodeDecodeTest{ + value: NewUnmeteredWord128ValueFromBigInt(sema.Word128TypeMaxIntBig), + encoded: []byte{ + 0xd8, CBORTagWord128Value, + // positive bignum + 0xc2, + // byte string, length 16 + 0x50, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + }, + }, + ) + }) + + t.Run("negative", func(t *testing.T) { + t.Parallel() + + testEncodeDecode(t, + encodeDecodeTest{ + encoded: []byte{ + 0xd8, CBORTagWord128Value, + // negative bignum + 0xc3, + // byte string, length 1 + 0x41, + 0x2a, + }, + invalid: true, + }, + ) + }) + + t.Run(">max", func(t *testing.T) { + t.Parallel() + + testEncodeDecode(t, + encodeDecodeTest{ + encoded: []byte{ + 0xd8, CBORTagWord128Value, + // positive bignum + 0xc2, + // byte string, length 17 + 0x51, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, + }, + invalid: true, + }, + ) + }) + + t.Run("RFC", func(t *testing.T) { + + t.Parallel() + + rfcValue, ok := new(big.Int).SetString("18446744073709551616", 10) + require.True(t, ok) + + testEncodeDecode(t, + encodeDecodeTest{ + value: NewUnmeteredWord128ValueFromBigInt(rfcValue), + encoded: []byte{ + // tag + 0xd8, CBORTagWord128Value, + // positive bignum + 0xc2, + // byte string, length 9 + 0x49, + 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }, + }, + ) + }) +} + func TestEncodeDecodeSomeValue(t *testing.T) { t.Parallel() diff --git a/runtime/interpreter/hashablevalue.go b/runtime/interpreter/hashablevalue.go index 84b4ecf5fa..76a2de96fc 100644 --- a/runtime/interpreter/hashablevalue.go +++ b/runtime/interpreter/hashablevalue.go @@ -90,7 +90,7 @@ const ( HashInputTypeWord16 HashInputTypeWord32 HashInputTypeWord64 - _ // future: Word128 + HashInputTypeWord128 _ // future: Word256 _ diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index fc43d2d505..7a61e17f32 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -1829,6 +1829,11 @@ func (interpreter *Interpreter) convert(value Value, valueType, targetType sema. return ConvertWord64(interpreter, value, locationRange) } + case sema.Word128Type: + if !valueType.Equal(unwrappedTargetType) { + return ConvertWord128(interpreter, value, locationRange) + } + // Fix* case sema.Fix64Type: @@ -2474,6 +2479,12 @@ var fromStringFunctionValues = func() map[string]fromStringFunctionValue { newFromStringFunction(sema.Word16Type, unsignedIntValueParser(16, NewWord16Value, u64_16)), newFromStringFunction(sema.Word32Type, unsignedIntValueParser(32, NewWord32Value, u64_32)), newFromStringFunction(sema.Word64Type, unsignedIntValueParser(64, NewWord64Value, u64_64)), + newFromStringFunction(sema.Word128Type, bigIntValueParser(func(b *big.Int) (v Value, ok bool) { + if ok = inRange(b, sema.Word128TypeMinIntBig, sema.Word128TypeMaxIntBig); ok { + v = NewUnmeteredWord128ValueFromBigInt(b) + } + return + })), // fixed-points newFromStringFunction(sema.Fix64Type, func(inter *Interpreter, input string) OptionalValue { @@ -2692,6 +2703,11 @@ var fromBigEndianBytesFunctionValues = func() map[string]fromBigEndianBytesFunct return val }) }), + newFromBigEndianBytesFunction(sema.Word128Type, 16, func(i *Interpreter, b []byte) Value { + return NewWord128ValueFromBigInt(i, func() *big.Int { + return BigEndianBytesToUnsignedBigInt(b) + }) + }), // fixed-points newFromBigEndianBytesFunction(sema.Fix64Type, 8, func(i *Interpreter, b []byte) Value { @@ -2892,6 +2908,15 @@ var ConverterDeclarations = []ValueConverterDeclaration{ min: NewUnmeteredWord64Value(0), max: NewUnmeteredWord64Value(math.MaxUint64), }, + { + name: sema.Word128TypeName, + functionType: sema.NumberConversionFunctionType(sema.Word128Type), + convert: func(interpreter *Interpreter, value Value, locationRange LocationRange) Value { + return ConvertWord128(interpreter, value, locationRange) + }, + min: NewUnmeteredWord128ValueFromUint64(0), + max: NewUnmeteredWord128ValueFromBigInt(sema.Word128TypeMaxIntBig), + }, { name: sema.Fix64TypeName, functionType: sema.NumberConversionFunctionType(sema.Fix64Type), diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index bda921e1de..210a5ec37b 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -682,6 +682,9 @@ func (interpreter *Interpreter) NewIntegerValueFromBigInt(value *big.Int, intege case sema.Word64Type: common.UseMemory(memoryGauge, word64MemoryUsage) return NewUnmeteredWord64Value(uint64(value.Int64())) + case sema.Word128Type: + // BigInt value is already metered at parser. + return NewUnmeteredWord128ValueFromBigInt(value) default: panic(errors.NewUnreachableError()) diff --git a/runtime/interpreter/primitivestatictype.go b/runtime/interpreter/primitivestatictype.go index 38bf308c1b..28bcc25177 100644 --- a/runtime/interpreter/primitivestatictype.go +++ b/runtime/interpreter/primitivestatictype.go @@ -151,7 +151,7 @@ const ( PrimitiveStaticTypeWord16 PrimitiveStaticTypeWord32 PrimitiveStaticTypeWord64 - _ // future: Word128 + PrimitiveStaticTypeWord128 _ // future: Word256 _ @@ -247,6 +247,7 @@ func (t PrimitiveStaticType) elementSize() uint { PrimitiveStaticTypeUInt256, PrimitiveStaticTypeInt128, PrimitiveStaticTypeInt256, + PrimitiveStaticTypeWord128, PrimitiveStaticTypeInteger, PrimitiveStaticTypeSignedInteger, PrimitiveStaticTypeNumber, @@ -394,6 +395,8 @@ func (i PrimitiveStaticType) SemaType() sema.Type { return sema.Word32Type case PrimitiveStaticTypeWord64: return sema.Word64Type + case PrimitiveStaticTypeWord128: + return sema.Word128Type // Fix* case PrimitiveStaticTypeFix64: @@ -524,6 +527,8 @@ func ConvertSemaToPrimitiveStaticType( typ = PrimitiveStaticTypeWord32 case sema.Word64Type: typ = PrimitiveStaticTypeWord64 + case sema.Word128Type: + typ = PrimitiveStaticTypeWord128 // Fix* case sema.Fix64Type: diff --git a/runtime/interpreter/primitivestatictype_string.go b/runtime/interpreter/primitivestatictype_string.go index 90d5dc8abb..1a927bcd0b 100644 --- a/runtime/interpreter/primitivestatictype_string.go +++ b/runtime/interpreter/primitivestatictype_string.go @@ -44,6 +44,7 @@ func _() { _ = x[PrimitiveStaticTypeWord16-54] _ = x[PrimitiveStaticTypeWord32-55] _ = x[PrimitiveStaticTypeWord64-56] + _ = x[PrimitiveStaticTypeWord128-57] _ = x[PrimitiveStaticTypeFix64-64] _ = x[PrimitiveStaticTypeUFix64-72] _ = x[PrimitiveStaticTypePath-76] @@ -70,7 +71,7 @@ func _() { _ = x[PrimitiveStaticType_Count-105] } -const _PrimitiveStaticType_name = "UnknownVoidAnyNeverAnyStructAnyResourceBoolAddressStringCharacterMetaTypeBlockNumberSignedNumberIntegerSignedIntegerFixedPointSignedFixedPointIntInt8Int16Int32Int64Int128Int256UIntUInt8UInt16UInt32UInt64UInt128UInt256Word8Word16Word32Word64Fix64UFix64PathCapabilityStoragePathCapabilityPathPublicPathPrivatePathAuthAccountPublicAccountDeployedContractAuthAccountContractsPublicAccountContractsAuthAccountKeysPublicAccountKeysAccountKeyAuthAccountInboxStorageCapabilityControllerAccountCapabilityControllerAuthAccountStorageCapabilitiesAuthAccountAccountCapabilitiesAuthAccountCapabilitiesPublicAccountCapabilities_Count" +const _PrimitiveStaticType_name = "UnknownVoidAnyNeverAnyStructAnyResourceBoolAddressStringCharacterMetaTypeBlockNumberSignedNumberIntegerSignedIntegerFixedPointSignedFixedPointIntInt8Int16Int32Int64Int128Int256UIntUInt8UInt16UInt32UInt64UInt128UInt256Word8Word16Word32Word64Word128Fix64UFix64PathCapabilityStoragePathCapabilityPathPublicPathPrivatePathAuthAccountPublicAccountDeployedContractAuthAccountContractsPublicAccountContractsAuthAccountKeysPublicAccountKeysAccountKeyAuthAccountInboxStorageCapabilityControllerAccountCapabilityControllerAuthAccountStorageCapabilitiesAuthAccountAccountCapabilitiesAuthAccountCapabilitiesPublicAccountCapabilities_Count" var _PrimitiveStaticType_map = map[PrimitiveStaticType]string{ 0: _PrimitiveStaticType_name[0:7], @@ -109,30 +110,31 @@ var _PrimitiveStaticType_map = map[PrimitiveStaticType]string{ 54: _PrimitiveStaticType_name[222:228], 55: _PrimitiveStaticType_name[228:234], 56: _PrimitiveStaticType_name[234:240], - 64: _PrimitiveStaticType_name[240:245], - 72: _PrimitiveStaticType_name[245:251], - 76: _PrimitiveStaticType_name[251:255], - 77: _PrimitiveStaticType_name[255:265], - 78: _PrimitiveStaticType_name[265:276], - 79: _PrimitiveStaticType_name[276:290], - 80: _PrimitiveStaticType_name[290:300], - 81: _PrimitiveStaticType_name[300:311], - 90: _PrimitiveStaticType_name[311:322], - 91: _PrimitiveStaticType_name[322:335], - 92: _PrimitiveStaticType_name[335:351], - 93: _PrimitiveStaticType_name[351:371], - 94: _PrimitiveStaticType_name[371:393], - 95: _PrimitiveStaticType_name[393:408], - 96: _PrimitiveStaticType_name[408:425], - 97: _PrimitiveStaticType_name[425:435], - 98: _PrimitiveStaticType_name[435:451], - 99: _PrimitiveStaticType_name[451:478], - 100: _PrimitiveStaticType_name[478:505], - 101: _PrimitiveStaticType_name[505:535], - 102: _PrimitiveStaticType_name[535:565], - 103: _PrimitiveStaticType_name[565:588], - 104: _PrimitiveStaticType_name[588:613], - 105: _PrimitiveStaticType_name[613:619], + 57: _PrimitiveStaticType_name[240:247], + 64: _PrimitiveStaticType_name[247:252], + 72: _PrimitiveStaticType_name[252:258], + 76: _PrimitiveStaticType_name[258:262], + 77: _PrimitiveStaticType_name[262:272], + 78: _PrimitiveStaticType_name[272:283], + 79: _PrimitiveStaticType_name[283:297], + 80: _PrimitiveStaticType_name[297:307], + 81: _PrimitiveStaticType_name[307:318], + 90: _PrimitiveStaticType_name[318:329], + 91: _PrimitiveStaticType_name[329:342], + 92: _PrimitiveStaticType_name[342:358], + 93: _PrimitiveStaticType_name[358:378], + 94: _PrimitiveStaticType_name[378:400], + 95: _PrimitiveStaticType_name[400:415], + 96: _PrimitiveStaticType_name[415:432], + 97: _PrimitiveStaticType_name[432:442], + 98: _PrimitiveStaticType_name[442:458], + 99: _PrimitiveStaticType_name[458:485], + 100: _PrimitiveStaticType_name[485:512], + 101: _PrimitiveStaticType_name[512:542], + 102: _PrimitiveStaticType_name[542:572], + 103: _PrimitiveStaticType_name[572:595], + 104: _PrimitiveStaticType_name[595:620], + 105: _PrimitiveStaticType_name[620:626], } func (i PrimitiveStaticType) String() string { diff --git a/runtime/interpreter/statictype_test.go b/runtime/interpreter/statictype_test.go index 509cbda6fe..9622ff675b 100644 --- a/runtime/interpreter/statictype_test.go +++ b/runtime/interpreter/statictype_test.go @@ -1208,6 +1208,11 @@ func TestStaticTypeConversion(t *testing.T) { semaType: sema.Word64Type, staticType: PrimitiveStaticTypeWord64, }, + { + name: "Word128", + semaType: sema.Word128Type, + staticType: PrimitiveStaticTypeWord128, + }, { name: "Fix64", diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index f49725f61a..af9c179601 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -12958,6 +12958,558 @@ func (Word64Value) ChildStorables() []atree.Storable { return nil } +// Word128Value + +type Word128Value struct { + BigInt *big.Int +} + +func NewWord128ValueFromUint64(memoryGauge common.MemoryGauge, value int64) Word128Value { + return NewWord128ValueFromBigInt( + memoryGauge, + func() *big.Int { + return new(big.Int).SetInt64(value) + }, + ) +} + +var Word128MemoryUsage = common.NewBigIntMemoryUsage(16) + +func NewWord128ValueFromBigInt(memoryGauge common.MemoryGauge, bigIntConstructor func() *big.Int) Word128Value { + common.UseMemory(memoryGauge, Int128MemoryUsage) + value := bigIntConstructor() + return NewUnmeteredWord128ValueFromBigInt(value) +} + +func NewUnmeteredWord128ValueFromUint64(value uint64) Word128Value { + return NewUnmeteredWord128ValueFromBigInt(new(big.Int).SetUint64(value)) +} + +func NewUnmeteredWord128ValueFromBigInt(value *big.Int) Word128Value { + return Word128Value{ + BigInt: value, + } +} + +var _ Value = Word128Value{} +var _ atree.Storable = Word128Value{} +var _ NumberValue = Word128Value{} +var _ IntegerValue = Word128Value{} +var _ EquatableValue = Word128Value{} +var _ ComparableValue = Word128Value{} +var _ HashableValue = Word128Value{} +var _ MemberAccessibleValue = Word128Value{} + +func (Word128Value) isValue() {} + +func (v Word128Value) Accept(interpreter *Interpreter, visitor Visitor) { + visitor.VisitWord128Value(interpreter, v) +} + +func (Word128Value) Walk(_ *Interpreter, _ func(Value)) { + // NO-OP +} + +func (Word128Value) StaticType(interpreter *Interpreter) StaticType { + return NewPrimitiveStaticType(interpreter, PrimitiveStaticTypeWord128) +} + +func (Word128Value) IsImportable(_ *Interpreter) bool { + return true +} + +func (v Word128Value) ToInt(locationRange LocationRange) int { + if !v.BigInt.IsInt64() { + panic(OverflowError{LocationRange: locationRange}) + } + return int(v.BigInt.Int64()) +} + +func (v Word128Value) ByteLength() int { + return common.BigIntByteLength(v.BigInt) +} + +func (v Word128Value) ToBigInt(memoryGauge common.MemoryGauge) *big.Int { + common.UseMemory(memoryGauge, common.NewBigIntMemoryUsage(v.ByteLength())) + return new(big.Int).Set(v.BigInt) +} + +func (v Word128Value) String() string { + return format.BigInt(v.BigInt) +} + +func (v Word128Value) RecursiveString(_ SeenReferences) string { + return v.String() +} + +func (v Word128Value) MeteredString(memoryGauge common.MemoryGauge, _ SeenReferences) string { + common.UseMemory( + memoryGauge, + common.NewRawStringMemoryUsage( + OverEstimateNumberStringLength(memoryGauge, v), + ), + ) + return v.String() +} + +func (v Word128Value) Negate(*Interpreter, LocationRange) NumberValue { + panic(errors.NewUnreachableError()) +} + +func (v Word128Value) Plus(interpreter *Interpreter, other NumberValue, locationRange LocationRange) NumberValue { + o, ok := other.(Word128Value) + if !ok { + panic(InvalidOperandsError{ + Operation: ast.OperationPlus, + LeftType: v.StaticType(interpreter), + RightType: other.StaticType(interpreter), + }) + } + + return NewWord128ValueFromBigInt( + interpreter, + func() *big.Int { + sum := new(big.Int) + sum.Add(v.BigInt, o.BigInt) + // Given that this value is backed by an arbitrary size integer, + // we can just add and wrap around in case of overflow. + // + // Note that since v and o are both in the range [0, 2**128 - 1), + // their sum will be in range [0, 2*(2**128 - 1)). + // Hence it is sufficient to subtract 2**128 to wrap around. + // + // If Go gains a native uint128 type and we switch this value + // to be based on it, then we need to follow INT30-C: + // + // if sum < v { + // ... + // } + // + if sum.Cmp(sema.Word128TypeMaxIntBig) > 0 { + sum.Sub(sum, sema.Word128TypeMaxIntPlusOneBig) + } + return sum + }, + ) +} + +func (v Word128Value) SaturatingPlus(interpreter *Interpreter, other NumberValue, locationRange LocationRange) NumberValue { + panic(errors.NewUnreachableError()) +} + +func (v Word128Value) Minus(interpreter *Interpreter, other NumberValue, locationRange LocationRange) NumberValue { + o, ok := other.(Word128Value) + if !ok { + panic(InvalidOperandsError{ + Operation: ast.OperationMinus, + LeftType: v.StaticType(interpreter), + RightType: other.StaticType(interpreter), + }) + } + + return NewWord128ValueFromBigInt( + interpreter, + func() *big.Int { + diff := new(big.Int) + diff.Sub(v.BigInt, o.BigInt) + // Given that this value is backed by an arbitrary size integer, + // we can just subtract and wrap around in case of underflow. + // + // Note that since v and o are both in the range [0, 2**128 - 1), + // their difference will be in range [-(2**128 - 1), 2**128 - 1). + // Hence it is sufficient to add 2**128 to wrap around. + // + // If Go gains a native uint128 type and we switch this value + // to be based on it, then we need to follow INT30-C: + // + // if diff > v { + // ... + // } + // + if diff.Sign() < 0 { + diff.Add(diff, sema.Word128TypeMaxIntPlusOneBig) + } + return diff + }, + ) +} + +func (v Word128Value) SaturatingMinus(interpreter *Interpreter, other NumberValue, locationRange LocationRange) NumberValue { + panic(errors.NewUnreachableError()) +} + +func (v Word128Value) Mod(interpreter *Interpreter, other NumberValue, locationRange LocationRange) NumberValue { + o, ok := other.(Word128Value) + if !ok { + panic(InvalidOperandsError{ + Operation: ast.OperationMod, + LeftType: v.StaticType(interpreter), + RightType: other.StaticType(interpreter), + }) + } + + return NewWord128ValueFromBigInt( + interpreter, + func() *big.Int { + res := new(big.Int) + if o.BigInt.Cmp(res) == 0 { + panic(DivisionByZeroError{LocationRange: locationRange}) + } + return res.Rem(v.BigInt, o.BigInt) + }, + ) +} + +func (v Word128Value) Mul(interpreter *Interpreter, other NumberValue, locationRange LocationRange) NumberValue { + o, ok := other.(Word128Value) + if !ok { + panic(InvalidOperandsError{ + Operation: ast.OperationMul, + LeftType: v.StaticType(interpreter), + RightType: other.StaticType(interpreter), + }) + } + + return NewWord128ValueFromBigInt( + interpreter, + func() *big.Int { + res := new(big.Int) + res.Mul(v.BigInt, o.BigInt) + if res.Cmp(sema.Word128TypeMaxIntBig) > 0 { + res.Mod(res, sema.Word128TypeMaxIntPlusOneBig) + } + return res + }, + ) +} + +func (v Word128Value) SaturatingMul(interpreter *Interpreter, other NumberValue, locationRange LocationRange) NumberValue { + panic(errors.NewUnreachableError()) +} + +func (v Word128Value) Div(interpreter *Interpreter, other NumberValue, locationRange LocationRange) NumberValue { + o, ok := other.(Word128Value) + if !ok { + panic(InvalidOperandsError{ + Operation: ast.OperationDiv, + LeftType: v.StaticType(interpreter), + RightType: other.StaticType(interpreter), + }) + } + + return NewWord128ValueFromBigInt( + interpreter, + func() *big.Int { + res := new(big.Int) + if o.BigInt.Cmp(res) == 0 { + panic(DivisionByZeroError{LocationRange: locationRange}) + } + return res.Div(v.BigInt, o.BigInt) + }, + ) + +} + +func (v Word128Value) SaturatingDiv(interpreter *Interpreter, other NumberValue, locationRange LocationRange) NumberValue { + panic(errors.NewUnreachableError()) +} + +func (v Word128Value) Less(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue { + o, ok := other.(Word128Value) + if !ok { + panic(InvalidOperandsError{ + Operation: ast.OperationLess, + LeftType: v.StaticType(interpreter), + RightType: other.StaticType(interpreter), + }) + } + + cmp := v.BigInt.Cmp(o.BigInt) + return AsBoolValue(cmp == -1) +} + +func (v Word128Value) LessEqual(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue { + o, ok := other.(Word128Value) + if !ok { + panic(InvalidOperandsError{ + Operation: ast.OperationLessEqual, + LeftType: v.StaticType(interpreter), + RightType: other.StaticType(interpreter), + }) + } + + cmp := v.BigInt.Cmp(o.BigInt) + return AsBoolValue(cmp <= 0) +} + +func (v Word128Value) Greater(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue { + o, ok := other.(Word128Value) + if !ok { + panic(InvalidOperandsError{ + Operation: ast.OperationGreater, + LeftType: v.StaticType(interpreter), + RightType: other.StaticType(interpreter), + }) + } + + cmp := v.BigInt.Cmp(o.BigInt) + return AsBoolValue(cmp == 1) +} + +func (v Word128Value) GreaterEqual(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue { + o, ok := other.(Word128Value) + if !ok { + panic(InvalidOperandsError{ + Operation: ast.OperationGreaterEqual, + LeftType: v.StaticType(interpreter), + RightType: other.StaticType(interpreter), + }) + } + + cmp := v.BigInt.Cmp(o.BigInt) + return AsBoolValue(cmp >= 0) +} + +func (v Word128Value) Equal(_ *Interpreter, _ LocationRange, other Value) bool { + otherInt, ok := other.(Word128Value) + if !ok { + return false + } + cmp := v.BigInt.Cmp(otherInt.BigInt) + return cmp == 0 +} + +// HashInput returns a byte slice containing: +// - HashInputTypeWord128 (1 byte) +// - big int encoded in big endian (n bytes) +func (v Word128Value) HashInput(_ *Interpreter, _ LocationRange, scratch []byte) []byte { + b := UnsignedBigIntToBigEndianBytes(v.BigInt) + + length := 1 + len(b) + var buffer []byte + if length <= len(scratch) { + buffer = scratch[:length] + } else { + buffer = make([]byte, length) + } + + buffer[0] = byte(HashInputTypeWord128) + copy(buffer[1:], b) + return buffer +} + +func ConvertWord128(memoryGauge common.MemoryGauge, value Value, locationRange LocationRange) Value { + return NewWord128ValueFromBigInt( + memoryGauge, + func() *big.Int { + + var v *big.Int + + switch value := value.(type) { + case BigNumberValue: + v = value.ToBigInt(memoryGauge) + + case NumberValue: + v = big.NewInt(int64(value.ToInt(locationRange))) + + default: + panic(errors.NewUnreachableError()) + } + + if v.Cmp(sema.Word128TypeMaxIntBig) > 0 || v.Sign() < 0 { + // When Sign() < 0, Mod will add sema.Word128TypeMaxIntPlusOneBig + // to ensure the range is [0, sema.Word128TypeMaxIntPlusOneBig) + v.Mod(v, sema.Word128TypeMaxIntPlusOneBig) + } + + return v + }, + ) +} + +func (v Word128Value) BitwiseOr(interpreter *Interpreter, other IntegerValue, locationRange LocationRange) IntegerValue { + o, ok := other.(Word128Value) + if !ok { + panic(InvalidOperandsError{ + Operation: ast.OperationBitwiseOr, + LeftType: v.StaticType(interpreter), + RightType: other.StaticType(interpreter), + }) + } + + return NewWord128ValueFromBigInt( + interpreter, + func() *big.Int { + res := new(big.Int) + return res.Or(v.BigInt, o.BigInt) + }, + ) +} + +func (v Word128Value) BitwiseXor(interpreter *Interpreter, other IntegerValue, locationRange LocationRange) IntegerValue { + o, ok := other.(Word128Value) + if !ok { + panic(InvalidOperandsError{ + Operation: ast.OperationBitwiseXor, + LeftType: v.StaticType(interpreter), + RightType: other.StaticType(interpreter), + }) + } + + return NewWord128ValueFromBigInt( + interpreter, + func() *big.Int { + res := new(big.Int) + return res.Xor(v.BigInt, o.BigInt) + }, + ) +} + +func (v Word128Value) BitwiseAnd(interpreter *Interpreter, other IntegerValue, locationRange LocationRange) IntegerValue { + o, ok := other.(Word128Value) + if !ok { + panic(InvalidOperandsError{ + Operation: ast.OperationBitwiseAnd, + LeftType: v.StaticType(interpreter), + RightType: other.StaticType(interpreter), + }) + } + + return NewWord128ValueFromBigInt( + interpreter, + func() *big.Int { + res := new(big.Int) + return res.And(v.BigInt, o.BigInt) + }, + ) + +} + +func (v Word128Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerValue, locationRange LocationRange) IntegerValue { + o, ok := other.(Word128Value) + if !ok { + panic(InvalidOperandsError{ + Operation: ast.OperationBitwiseLeftShift, + LeftType: v.StaticType(interpreter), + RightType: other.StaticType(interpreter), + }) + } + + return NewWord128ValueFromBigInt( + interpreter, + func() *big.Int { + res := new(big.Int) + if o.BigInt.Sign() < 0 { + panic(UnderflowError{LocationRange: locationRange}) + } + if !o.BigInt.IsUint64() { + panic(OverflowError{LocationRange: locationRange}) + } + return res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) + }, + ) +} + +func (v Word128Value) BitwiseRightShift(interpreter *Interpreter, other IntegerValue, locationRange LocationRange) IntegerValue { + o, ok := other.(Word128Value) + if !ok { + panic(InvalidOperandsError{ + Operation: ast.OperationBitwiseRightShift, + LeftType: v.StaticType(interpreter), + RightType: other.StaticType(interpreter), + }) + } + + return NewWord128ValueFromBigInt( + interpreter, + func() *big.Int { + res := new(big.Int) + if o.BigInt.Sign() < 0 { + panic(UnderflowError{LocationRange: locationRange}) + } + if !o.BigInt.IsUint64() { + panic(OverflowError{LocationRange: locationRange}) + } + return res.Rsh(v.BigInt, uint(o.BigInt.Uint64())) + }, + ) +} + +func (v Word128Value) GetMember(interpreter *Interpreter, locationRange LocationRange, name string) Value { + return getNumberValueMember(interpreter, v, name, sema.Word128Type, locationRange) +} + +func (Word128Value) RemoveMember(_ *Interpreter, _ LocationRange, _ string) Value { + // Numbers have no removable members (fields / functions) + panic(errors.NewUnreachableError()) +} + +func (Word128Value) SetMember(_ *Interpreter, _ LocationRange, _ string, _ Value) bool { + // Numbers have no settable members (fields / functions) + panic(errors.NewUnreachableError()) +} + +func (v Word128Value) ToBigEndianBytes() []byte { + return UnsignedBigIntToBigEndianBytes(v.BigInt) +} + +func (v Word128Value) ConformsToStaticType( + _ *Interpreter, + _ LocationRange, + _ TypeConformanceResults, +) bool { + return true +} + +func (Word128Value) IsStorable() bool { + return true +} + +func (v Word128Value) Storable(_ atree.SlabStorage, _ atree.Address, _ uint64) (atree.Storable, error) { + return v, nil +} + +func (Word128Value) NeedsStoreTo(_ atree.Address) bool { + return false +} + +func (Word128Value) IsResourceKinded(_ *Interpreter) bool { + return false +} + +func (v Word128Value) Transfer( + interpreter *Interpreter, + _ LocationRange, + _ atree.Address, + remove bool, + storable atree.Storable, +) Value { + if remove { + interpreter.RemoveReferencedSlab(storable) + } + return v +} + +func (v Word128Value) Clone(_ *Interpreter) Value { + return NewUnmeteredWord128ValueFromBigInt(v.BigInt) +} + +func (Word128Value) DeepRemove(_ *Interpreter) { + // NO-OP +} + +func (v Word128Value) ByteSize() uint32 { + return cborTagSize + getBigIntCBORSize(v.BigInt) +} + +func (v Word128Value) StoredValue(_ atree.SlabStorage) (atree.Value, error) { + return v, nil +} + +func (Word128Value) ChildStorables() []atree.Storable { + return nil +} + // FixedPointValue is a fixed-point number value type FixedPointValue interface { NumberValue diff --git a/runtime/interpreter/value_test.go b/runtime/interpreter/value_test.go index f19be940d5..a10a3f3427 100644 --- a/runtime/interpreter/value_test.go +++ b/runtime/interpreter/value_test.go @@ -961,6 +961,10 @@ func TestStringer(t *testing.T) { value: NewUnmeteredWord64Value(64), expected: "64", }, + "Word128": { + value: NewUnmeteredWord128ValueFromUint64(64), + expected: "64", + }, "UFix64": { value: NewUnmeteredUFix64ValueWithInteger(64, EmptyLocationRange), expected: "64.00000000", @@ -1455,6 +1459,18 @@ func TestGetHashInput(t *testing.T) { value: NewUnmeteredWord64Value(math.MaxUint64), expected: []byte{byte(HashInputTypeWord64), 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, }, + "Word128": { + value: NewUnmeteredWord128ValueFromUint64(128), + expected: []byte{byte(HashInputTypeWord128), 128}, + }, + "Word128 min": { + value: NewUnmeteredWord128ValueFromUint64(0), + expected: append([]byte{byte(HashInputTypeWord128)}, 0), + }, + "Word128 max": { + value: NewUnmeteredWord128ValueFromBigInt(sema.Word128TypeMaxIntBig), + expected: append([]byte{byte(HashInputTypeWord128)}, sema.Word128TypeMaxIntBig.Bytes()...), + }, "UFix64": { value: NewUnmeteredUFix64ValueWithInteger(64, EmptyLocationRange), expected: []byte{byte(HashInputTypeUFix64), 0x0, 0x0, 0x0, 0x1, 0x7d, 0x78, 0x40, 0x0}, @@ -3379,6 +3395,7 @@ func TestNumberValue_Equal(t *testing.T) { "Word16": NewUnmeteredWord16Value(16), "Word32": NewUnmeteredWord32Value(32), "Word64": NewUnmeteredWord64Value(64), + "Word128": NewUnmeteredWord128ValueFromUint64(128), "UFix64": NewUnmeteredUFix64ValueWithInteger(64, EmptyLocationRange), "Fix64": NewUnmeteredFix64ValueWithInteger(-32, EmptyLocationRange), } @@ -3757,6 +3774,7 @@ func TestNumberValueIntegerConversion(t *testing.T) { sema.Word16Type: NewUnmeteredWord16Value(42), sema.Word32Type: NewUnmeteredWord32Value(42), sema.Word64Type: NewUnmeteredWord64Value(42), + sema.Word128Type: NewUnmeteredWord128ValueFromUint64(42), sema.Int8Type: NewUnmeteredInt8Value(42), sema.Int16Type: NewUnmeteredInt16Value(42), sema.Int32Type: NewUnmeteredInt32Value(42), @@ -4059,6 +4077,7 @@ func TestValue_ConformsToStaticType(t *testing.T) { sema.Word16Type: NewUnmeteredWord16Value(42), sema.Word32Type: NewUnmeteredWord32Value(42), sema.Word64Type: NewUnmeteredWord64Value(42), + sema.Word128Type: NewUnmeteredWord128ValueFromUint64(42), sema.Int8Type: NewUnmeteredInt8Value(42), sema.Int16Type: NewUnmeteredInt16Value(42), sema.Int32Type: NewUnmeteredInt32Value(42), diff --git a/runtime/interpreter/visitor.go b/runtime/interpreter/visitor.go index 96a5e8efdd..6f11f15541 100644 --- a/runtime/interpreter/visitor.go +++ b/runtime/interpreter/visitor.go @@ -44,6 +44,7 @@ type Visitor interface { VisitWord16Value(interpreter *Interpreter, value Word16Value) VisitWord32Value(interpreter *Interpreter, value Word32Value) VisitWord64Value(interpreter *Interpreter, value Word64Value) + VisitWord128Value(interpreter *Interpreter, value Word128Value) VisitFix64Value(interpreter *Interpreter, value Fix64Value) VisitUFix64Value(interpreter *Interpreter, value UFix64Value) VisitCompositeValue(interpreter *Interpreter, value *CompositeValue) bool @@ -93,6 +94,7 @@ type EmptyVisitor struct { Word16ValueVisitor func(interpreter *Interpreter, value Word16Value) Word32ValueVisitor func(interpreter *Interpreter, value Word32Value) Word64ValueVisitor func(interpreter *Interpreter, value Word64Value) + Word128ValueVisitor func(interpreter *Interpreter, value Word128Value) Fix64ValueVisitor func(interpreter *Interpreter, value Fix64Value) UFix64ValueVisitor func(interpreter *Interpreter, value UFix64Value) CompositeValueVisitor func(interpreter *Interpreter, value *CompositeValue) bool @@ -293,6 +295,13 @@ func (v EmptyVisitor) VisitWord64Value(interpreter *Interpreter, value Word64Val v.Word64ValueVisitor(interpreter, value) } +func (v EmptyVisitor) VisitWord128Value(interpreter *Interpreter, value Word128Value) { + if v.Word128ValueVisitor == nil { + return + } + v.Word128ValueVisitor(interpreter, value) +} + func (v EmptyVisitor) VisitFix64Value(interpreter *Interpreter, value Fix64Value) { if v.Fix64ValueVisitor == nil { return diff --git a/runtime/literal.go b/runtime/literal.go index 1878023e50..d1bc69ba75 100644 --- a/runtime/literal.go +++ b/runtime/literal.go @@ -255,6 +255,8 @@ func convertIntValue( return interpreter.ConvertWord32(memoryGauge, intValue, interpreter.EmptyLocationRange), nil case sema.Word64Type: return interpreter.ConvertWord64(memoryGauge, intValue, interpreter.EmptyLocationRange), nil + case sema.Word128Type: + return interpreter.ConvertWord128(memoryGauge, intValue, interpreter.EmptyLocationRange), nil default: return nil, UnsupportedLiteralError diff --git a/runtime/sema/type.go b/runtime/sema/type.go index bb1e88479f..45b8e058df 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -1515,6 +1515,12 @@ var ( WithTag(Word64TypeTag). WithIntRange(Word64TypeMinInt, Word64TypeMaxInt) + // Word128Type represents the 128-bit unsigned integer type `Word128` + // which does NOT check for overflow and underflow + Word128Type = NewNumericType(Word128TypeName). + WithTag(Word128TypeTag). + WithIntRange(Word128TypeMinIntBig, Word128TypeMaxIntBig) + // FixedPointType represents the super-type of all fixed-point types FixedPointType = NewNumericType(FixedPointTypeName). WithTag(FixedPointTypeTag). @@ -1634,6 +1640,19 @@ var ( Word64TypeMinInt = new(big.Int) Word64TypeMaxInt = new(big.Int).SetUint64(math.MaxUint64) + // 1 << 128 + Word128TypeMaxIntPlusOneBig = func() *big.Int { + word128TypeMaxPlusOne := big.NewInt(1) + word128TypeMaxPlusOne.Lsh(word128TypeMaxPlusOne, 128) + return word128TypeMaxPlusOne + }() + Word128TypeMinIntBig = new(big.Int) + Word128TypeMaxIntBig = func() *big.Int { + word128TypeMax := new(big.Int) + word128TypeMax.Sub(Word128TypeMaxIntPlusOneBig, big.NewInt(1)) + return word128TypeMax + }() + Fix64FactorBig = new(big.Int).SetUint64(uint64(Fix64Factor)) Fix64TypeMinIntBig = fixedpoint.Fix64TypeMinIntBig @@ -3197,6 +3216,7 @@ var AllUnsignedIntegerTypes = []Type{ Word16Type, Word32Type, Word64Type, + Word128Type, } var AllIntegerTypes = append( @@ -5407,7 +5427,7 @@ func checkSubTypeWithoutEquality(subType Type, superType Type) bool { case IntegerType, SignedIntegerType, UIntType, UInt8Type, UInt16Type, UInt32Type, UInt64Type, UInt128Type, UInt256Type, - Word8Type, Word16Type, Word32Type, Word64Type: + Word8Type, Word16Type, Word32Type, Word64Type, Word128Type: return true diff --git a/runtime/sema/type_names.go b/runtime/sema/type_names.go index 3be08052a2..d156f0d43b 100644 --- a/runtime/sema/type_names.go +++ b/runtime/sema/type_names.go @@ -42,10 +42,11 @@ const ( UInt128TypeName = "UInt128" UInt256TypeName = "UInt256" - Word8TypeName = "Word8" - Word16TypeName = "Word16" - Word32TypeName = "Word32" - Word64TypeName = "Word64" + Word8TypeName = "Word8" + Word16TypeName = "Word16" + Word32TypeName = "Word32" + Word64TypeName = "Word64" + Word128TypeName = "Word128" Fix64TypeName = "Fix64" UFix64TypeName = "UFix64" diff --git a/runtime/sema/type_tags.go b/runtime/sema/type_tags.go index b20714cf0d..7255f52a22 100644 --- a/runtime/sema/type_tags.go +++ b/runtime/sema/type_tags.go @@ -164,6 +164,7 @@ const ( word16TypeMask word32TypeMask word64TypeMask + word128TypeMask _ // future: Fix8 _ // future: Fix16 @@ -206,7 +207,6 @@ const ( referenceTypeMask genericTypeMask functionTypeMask - interfaceTypeMask // ~~ NOTE: End of limit for lower mask type. Any new type should go to upper mask. ~~ ) @@ -221,6 +221,8 @@ const ( storageCapabilityControllerTypeMask accountCapabilityControllerTypeMask + interfaceTypeMask + invalidTypeMask ) @@ -248,7 +250,8 @@ var ( Or(Word8TypeTag). Or(Word16TypeTag). Or(Word32TypeTag). - Or(Word64TypeTag) + Or(Word64TypeTag). + Or(Word128TypeTag) IntegerTypeTag = newTypeTagFromLowerMask(integerTypeMask). Or(SignedIntegerTypeTag). @@ -289,10 +292,11 @@ var ( Int128TypeTag = newTypeTagFromLowerMask(int128TypeMask) Int256TypeTag = newTypeTagFromLowerMask(int256TypeMask) - Word8TypeTag = newTypeTagFromLowerMask(word8TypeMask) - Word16TypeTag = newTypeTagFromLowerMask(word16TypeMask) - Word32TypeTag = newTypeTagFromLowerMask(word32TypeMask) - Word64TypeTag = newTypeTagFromLowerMask(word64TypeMask) + Word8TypeTag = newTypeTagFromLowerMask(word8TypeMask) + Word16TypeTag = newTypeTagFromLowerMask(word16TypeMask) + Word32TypeTag = newTypeTagFromLowerMask(word32TypeMask) + Word64TypeTag = newTypeTagFromLowerMask(word64TypeMask) + Word128TypeTag = newTypeTagFromLowerMask(word128TypeMask) Fix64TypeTag = newTypeTagFromLowerMask(fix64TypeMask) UFix64TypeTag = newTypeTagFromLowerMask(ufix64TypeMask) @@ -327,7 +331,7 @@ var ( ReferenceTypeTag = newTypeTagFromLowerMask(referenceTypeMask) GenericTypeTag = newTypeTagFromLowerMask(genericTypeMask) FunctionTypeTag = newTypeTagFromLowerMask(functionTypeMask) - InterfaceTypeTag = newTypeTagFromLowerMask(interfaceTypeMask) + InterfaceTypeTag = newTypeTagFromUpperMask(interfaceTypeMask) RestrictedTypeTag = newTypeTagFromUpperMask(restrictedTypeMask) CapabilityTypeTag = newTypeTagFromUpperMask(capabilityTypeMask) @@ -561,6 +565,8 @@ func findSuperTypeFromLowerMask(joinedTypeTag TypeTag, types []Type) Type { return Word32Type case word64TypeMask: return Word64Type + case word128TypeMask: + return Word128Type case fix64TypeMask: return Fix64Type @@ -640,8 +646,7 @@ func findSuperTypeFromLowerMask(joinedTypeTag TypeTag, types []Type) Type { return commonSuperTypeOfDictionaries(types) case referenceTypeMask, genericTypeMask, - functionTypeMask, - interfaceTypeMask: + functionTypeMask: return getSuperTypeOfDerivedTypes(types) default: @@ -659,7 +664,8 @@ func findSuperTypeFromUpperMask(joinedTypeTag TypeTag, types []Type) Type { // All derived types goes here. case capabilityTypeMask, restrictedTypeMask, - transactionTypeMask: + transactionTypeMask, + interfaceTypeMask: return getSuperTypeOfDerivedTypes(types) case anyResourceAttachmentMask: diff --git a/runtime/sema/type_test.go b/runtime/sema/type_test.go index e7d6ebb41a..0e27d87bba 100644 --- a/runtime/sema/type_test.go +++ b/runtime/sema/type_test.go @@ -816,6 +816,7 @@ func TestCommonSuperType(t *testing.T) { UInt256Type, IntegerType, Word64Type, + Word128Type, }, expectedSuperType: IntegerType, }, diff --git a/runtime/tests/interpreter/arithmetic_test.go b/runtime/tests/interpreter/arithmetic_test.go index af8cb46963..23a9d975bf 100644 --- a/runtime/tests/interpreter/arithmetic_test.go +++ b/runtime/tests/interpreter/arithmetic_test.go @@ -49,10 +49,11 @@ var integerTestValues = map[string]interpreter.NumberValue{ "UInt128": interpreter.NewUnmeteredUInt128ValueFromUint64(60), "UInt256": interpreter.NewUnmeteredUInt256ValueFromUint64(60), // Word* - "Word8": interpreter.NewUnmeteredWord8Value(60), - "Word16": interpreter.NewUnmeteredWord16Value(60), - "Word32": interpreter.NewUnmeteredWord32Value(60), - "Word64": interpreter.NewUnmeteredWord64Value(60), + "Word8": interpreter.NewUnmeteredWord8Value(60), + "Word16": interpreter.NewUnmeteredWord16Value(60), + "Word32": interpreter.NewUnmeteredWord32Value(60), + "Word64": interpreter.NewUnmeteredWord64Value(60), + "Word128": interpreter.NewUnmeteredWord128ValueFromUint64(60), } func init() { diff --git a/runtime/tests/interpreter/bitwise_test.go b/runtime/tests/interpreter/bitwise_test.go index 5319dd7fac..00f9d430d1 100644 --- a/runtime/tests/interpreter/bitwise_test.go +++ b/runtime/tests/interpreter/bitwise_test.go @@ -85,6 +85,9 @@ var bitwiseTestValueFunctions = map[string]func(int) interpreter.NumberValue{ "Word64": func(v int) interpreter.NumberValue { return interpreter.NewUnmeteredWord64Value(uint64(v)) }, + "Word128": func(v int) interpreter.NumberValue { + return interpreter.NewUnmeteredWord128ValueFromUint64(uint64(v)) + }, } func init() { diff --git a/runtime/tests/interpreter/builtinfunctions_test.go b/runtime/tests/interpreter/builtinfunctions_test.go index 33eb196ad5..c8a74a04d1 100644 --- a/runtime/tests/interpreter/builtinfunctions_test.go +++ b/runtime/tests/interpreter/builtinfunctions_test.go @@ -437,7 +437,9 @@ func TestInterpretToBigEndianBytes(t *testing.T) { "42": {42}, "127": {127}, "128": {128}, - "200": {200}, + "170141183460469231731687303715884105727": {127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + "170141183460469231731687303715884105728": {128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + "340282366920938463463374607431768211455": {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, }, "UInt256": { "0": {0}, @@ -475,6 +477,15 @@ func TestInterpretToBigEndianBytes(t *testing.T) { "9223372036854775808": {128, 0, 0, 0, 0, 0, 0, 0}, "18446744073709551615": {255, 255, 255, 255, 255, 255, 255, 255}, }, + "Word128": { + "0": {0}, + "42": {42}, + "127": {127}, + "128": {128}, + "170141183460469231731687303715884105727": {127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + "170141183460469231731687303715884105728": {128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + "340282366920938463463374607431768211455": {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, // Fix* "Fix64": { "0.0": {0, 0, 0, 0, 0, 0, 0, 0}, @@ -689,6 +700,13 @@ func TestInterpretFromBigEndianBytes(t *testing.T) { "[128, 0, 0, 0, 0, 0, 0, 0]": interpreter.NewUnmeteredWord64Value(9223372036854775808), "[255, 255, 255, 255, 255, 255, 255, 255]": interpreter.NewUnmeteredWord64Value(18446744073709551615), }, + "Word128": { + "[0]": interpreter.NewUnmeteredWord128ValueFromBigInt(big.NewInt(0)), + "[42]": interpreter.NewUnmeteredWord128ValueFromBigInt(big.NewInt(42)), + "[127]": interpreter.NewUnmeteredWord128ValueFromBigInt(big.NewInt(127)), + "[128]": interpreter.NewUnmeteredWord128ValueFromBigInt(big.NewInt(128)), + "[200]": interpreter.NewUnmeteredWord128ValueFromBigInt(big.NewInt(200)), + }, // Fix* "Fix64": { "[0, 0, 0, 0, 0, 0, 0, 0]": interpreter.NewUnmeteredFix64Value(0), @@ -776,6 +794,10 @@ func TestInterpretFromBigEndianBytes(t *testing.T) { "[0, 0, 0, 0, 0, 0, 0, 0, 0]", "[0, 22, 0, 0, 0, 0, 0, 0, 0]", }, + "Word128": { + "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]", + "[0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]", + }, // Fix* "Fix64": { "[0, 0, 0, 0, 0, 0, 0, 0, 0]", diff --git a/runtime/tests/interpreter/dynamic_casting_test.go b/runtime/tests/interpreter/dynamic_casting_test.go index b9638076f5..147ffc9e40 100644 --- a/runtime/tests/interpreter/dynamic_casting_test.go +++ b/runtime/tests/interpreter/dynamic_casting_test.go @@ -71,6 +71,7 @@ func TestInterpretDynamicCastingNumber(t *testing.T) { {sema.Word16Type, "42", interpreter.NewUnmeteredWord16Value(42)}, {sema.Word32Type, "42", interpreter.NewUnmeteredWord32Value(42)}, {sema.Word64Type, "42", interpreter.NewUnmeteredWord64Value(42)}, + {sema.Word128Type, "42", interpreter.NewUnmeteredWord128ValueFromUint64(42)}, {sema.Fix64Type, "1.23", interpreter.NewUnmeteredFix64Value(123000000)}, {sema.UFix64Type, "1.23", interpreter.NewUnmeteredUFix64Value(123000000)}, } diff --git a/runtime/tests/interpreter/equality_test.go b/runtime/tests/interpreter/equality_test.go index 558a105023..0d644043bf 100644 --- a/runtime/tests/interpreter/equality_test.go +++ b/runtime/tests/interpreter/equality_test.go @@ -228,6 +228,7 @@ func TestInterpretEqualityOnNumericSuperTypes(t *testing.T) { interpreter.PrimitiveStaticTypeWord16, interpreter.PrimitiveStaticTypeWord32, interpreter.PrimitiveStaticTypeWord64, + interpreter.PrimitiveStaticTypeWord128, } for _, subtype := range intSubtypes { diff --git a/runtime/tests/interpreter/fixedpoint_test.go b/runtime/tests/interpreter/fixedpoint_test.go index 6d90153f7e..b8c4bc6145 100644 --- a/runtime/tests/interpreter/fixedpoint_test.go +++ b/runtime/tests/interpreter/fixedpoint_test.go @@ -387,6 +387,7 @@ func TestInterpretFixedPointConversions(t *testing.T) { bigIntegerTypes := []sema.Type{ sema.Word64Type, + sema.Word128Type, sema.UInt64Type, sema.UInt128Type, sema.UInt256Type, diff --git a/runtime/tests/interpreter/integers_test.go b/runtime/tests/interpreter/integers_test.go index 3741533052..6b8461dc10 100644 --- a/runtime/tests/interpreter/integers_test.go +++ b/runtime/tests/interpreter/integers_test.go @@ -52,10 +52,11 @@ var testIntegerTypesAndValues = map[string]interpreter.Value{ "UInt128": interpreter.NewUnmeteredUInt128ValueFromUint64(50), "UInt256": interpreter.NewUnmeteredUInt256ValueFromUint64(50), // Word* - "Word8": interpreter.NewUnmeteredWord8Value(50), - "Word16": interpreter.NewUnmeteredWord16Value(50), - "Word32": interpreter.NewUnmeteredWord32Value(50), - "Word64": interpreter.NewUnmeteredWord64Value(50), + "Word8": interpreter.NewUnmeteredWord8Value(50), + "Word16": interpreter.NewUnmeteredWord16Value(50), + "Word32": interpreter.NewUnmeteredWord32Value(50), + "Word64": interpreter.NewUnmeteredWord64Value(50), + "Word128": interpreter.NewUnmeteredWord128ValueFromUint64(50), } func init() { @@ -121,10 +122,11 @@ func TestInterpretWordOverflowConversions(t *testing.T) { t.Parallel() words := map[string]*big.Int{ - "Word8": sema.UInt8TypeMaxInt, - "Word16": sema.UInt16TypeMaxInt, - "Word32": sema.UInt32TypeMaxInt, - "Word64": sema.UInt64TypeMaxInt, + "Word8": sema.UInt8TypeMaxInt, + "Word16": sema.UInt16TypeMaxInt, + "Word32": sema.UInt32TypeMaxInt, + "Word64": sema.UInt64TypeMaxInt, + "Word128": sema.UInt128TypeMaxIntBig, } for typeName, value := range words { @@ -156,10 +158,11 @@ func TestInterpretWordUnderflowConversions(t *testing.T) { t.Parallel() words := map[string]*big.Int{ - "Word8": sema.UInt8TypeMaxInt, - "Word16": sema.UInt16TypeMaxInt, - "Word32": sema.UInt32TypeMaxInt, - "Word64": sema.UInt64TypeMaxInt, + "Word8": sema.UInt8TypeMaxInt, + "Word16": sema.UInt16TypeMaxInt, + "Word32": sema.UInt32TypeMaxInt, + "Word64": sema.UInt64TypeMaxInt, + "Word128": sema.Word128TypeMaxIntBig, } for typeName, value := range words { @@ -654,6 +657,11 @@ func TestInterpretIntegerConversion(t *testing.T) { min: interpreter.NewUnmeteredWord64Value(0), max: interpreter.NewUnmeteredWord64Value(math.MaxUint64), }, + sema.Word128Type: { + fortyTwo: interpreter.NewUnmeteredWord128ValueFromUint64(42), + min: interpreter.NewUnmeteredWord128ValueFromUint64(0), + max: interpreter.NewUnmeteredWord128ValueFromBigInt(sema.Word128TypeMaxIntBig), + }, sema.Int8Type: { fortyTwo: interpreter.NewUnmeteredInt8Value(42), min: interpreter.NewUnmeteredInt8Value(math.MinInt8), @@ -713,7 +721,8 @@ func TestInterpretIntegerConversion(t *testing.T) { case sema.Word8Type, sema.Word16Type, sema.Word32Type, - sema.Word64Type: + sema.Word64Type, + sema.Word128Type: default: t.Run("underflow", func(t *testing.T) { test(t, sourceType, targetType, sourceValues.min, nil, interpreter.UnderflowError{}) @@ -738,7 +747,8 @@ func TestInterpretIntegerConversion(t *testing.T) { case sema.Word8Type, sema.Word16Type, sema.Word32Type, - sema.Word64Type: + sema.Word64Type, + sema.Word128Type: default: t.Run("overflow", func(t *testing.T) { test(t, sourceType, targetType, sourceValues.max, nil, interpreter.OverflowError{}) @@ -834,6 +844,10 @@ func TestInterpretIntegerMinMax(t *testing.T) { min: interpreter.NewUnmeteredWord64Value(0), max: interpreter.NewUnmeteredWord64Value(math.MaxUint64), }, + sema.Word128Type: { + min: interpreter.NewUnmeteredWord128ValueFromUint64(0), + max: interpreter.NewUnmeteredWord128ValueFromBigInt(sema.Word128TypeMaxIntBig), + }, sema.Int8Type: { min: interpreter.NewUnmeteredInt8Value(math.MinInt8), max: interpreter.NewUnmeteredInt8Value(math.MaxInt8), diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index 59087b5a86..6f94325eeb 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -7252,6 +7252,10 @@ func TestInterpretEmitEventParameterTypes(t *testing.T) { value: interpreter.NewUnmeteredWord64Value(42), ty: sema.Word64Type, }, + "Word128": { + value: interpreter.NewUnmeteredWord128ValueFromUint64(42), + ty: sema.Word128Type, + }, // Fix* "Fix64": { value: interpreter.NewUnmeteredFix64Value(123000000), diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 4e8b6cc3f3..b10c3fd8ca 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -1219,6 +1219,8 @@ func generateRandomHashableValue(inter *interpreter.Interpreter, n int) interpre return interpreter.NewUnmeteredWord32Value(rand.Uint32()) case Word64: return interpreter.NewUnmeteredWord64Value(rand.Uint64()) + case Word128: + return interpreter.NewUnmeteredWord128ValueFromUint64(rand.Uint64()) // Fixed point case Fix64: @@ -1486,6 +1488,8 @@ func intSubtype(n int) sema.Type { return sema.Word32Type case Word64: return sema.Word64Type + case Word128: + return sema.Word128Type default: panic(fmt.Sprintf("unsupported: %d", n)) @@ -1517,6 +1521,7 @@ const ( Word16 Word32 Word64 + Word128 Fix64 UFix64 diff --git a/types.go b/types.go index 0b5842d9a8..8adf7b6434 100644 --- a/types.go +++ b/types.go @@ -824,6 +824,26 @@ func (t Word64Type) Equal(other Type) bool { return t == other } +// Word128Type + +type Word128Type struct{} + +var TheWord128Type = Word128Type{} + +func NewWord128Type() Word128Type { + return TheWord128Type +} + +func (Word128Type) isType() {} + +func (Word128Type) ID() string { + return "Word128" +} + +func (t Word128Type) Equal(other Type) bool { + return t == other +} + // Fix64Type type Fix64Type struct{} diff --git a/types_test.go b/types_test.go index d927e17230..1d493c82c5 100644 --- a/types_test.go +++ b/types_test.go @@ -64,6 +64,7 @@ func TestType_ID(t *testing.T) { {Word16Type{}, "Word16"}, {Word32Type{}, "Word32"}, {Word64Type{}, "Word64"}, + {Word128Type{}, "Word128"}, {UFix64Type{}, "UFix64"}, {Fix64Type{}, "Fix64"}, {VoidType{}, "Void"}, @@ -297,6 +298,7 @@ func TestTypeEquality(t *testing.T) { Word16Type{}, Word32Type{}, Word64Type{}, + Word128Type{}, UFix64Type{}, Fix64Type{}, VoidType{}, diff --git a/values.go b/values.go index a28bb86ee4..11d071e81e 100644 --- a/values.go +++ b/values.go @@ -1229,6 +1229,74 @@ func (v Word64) String() string { return format.Uint(uint64(v)) } +// Word128 + +type Word128 struct { + Value *big.Int +} + +var _ Value = Word128{} + +var Word128MemoryUsage = common.NewCadenceBigIntMemoryUsage(16) + +func NewWord128(i uint) Word128 { + return Word128{ + Value: big.NewInt(int64(i)), + } +} + +var word128NegativeError = errors.NewDefaultUserError("invalid negative value for Word128") +var word128MaxExceededError = errors.NewDefaultUserError("value exceeds max of Word128") + +func NewWord128FromBig(i *big.Int) (Word128, error) { + if i.Sign() < 0 { + return Word128{}, word128NegativeError + } + if i.Cmp(sema.Word128TypeMaxIntBig) > 0 { + return Word128{}, word128MaxExceededError + } + return Word128{Value: i}, nil +} + +func NewMeteredWord128FromBig( + memoryGauge common.MemoryGauge, + bigIntConstructor func() *big.Int, +) (Word128, error) { + common.UseMemory(memoryGauge, Word128MemoryUsage) + value := bigIntConstructor() + return NewWord128FromBig(value) +} + +func (Word128) isValue() {} + +func (Word128) Type() Type { + return TheWord128Type +} + +func (v Word128) MeteredType(common.MemoryGauge) Type { + return v.Type() +} + +func (v Word128) ToGoValue() any { + return v.Big() +} + +func (v Word128) Int() int { + return int(v.Value.Uint64()) +} + +func (v Word128) Big() *big.Int { + return v.Value +} + +func (v Word128) ToBigEndianBytes() []byte { + return interpreter.UnsignedBigIntToBigEndianBytes(v.Value) +} + +func (v Word128) String() string { + return format.BigInt(v.Value) +} + // Fix64 type Fix64 int64 diff --git a/values_test.go b/values_test.go index fc37ab7736..6a09f9bdc0 100644 --- a/values_test.go +++ b/values_test.go @@ -146,6 +146,11 @@ func newValueTestCases() map[string]valueTestCase { string: "64", expectedType: Word64Type{}, }, + "Word128": { + value: NewWord128(128), + string: "128", + expectedType: Word128Type{}, + }, "UFix64": { value: ufix64, string: "64.01000000", @@ -450,6 +455,12 @@ func TestNumberValue_ToBigEndianBytes(t *testing.T) { t.Parallel() + uint128LargeValueTestCase, _ := NewUInt128FromBig(new(big.Int).SetBytes([]byte{127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255})) + uint128MaxValue, _ := NewUInt128FromBig(sema.UInt128TypeMaxIntBig) + + word128LargeValueTestCase, _ := NewWord128FromBig(new(big.Int).SetBytes([]byte{127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255})) + word128MaxValue, _ := NewWord128FromBig(sema.Word128TypeMaxIntBig) + typeTests := map[string]map[NumberValue][]byte{ // Int* "Int": { @@ -551,11 +562,13 @@ func TestNumberValue_ToBigEndianBytes(t *testing.T) { NewUInt64(18446744073709551615): {255, 255, 255, 255, 255, 255, 255, 255}, }, "UInt128": { - NewUInt128(0): {0}, - NewUInt128(42): {42}, - NewUInt128(127): {127}, - NewUInt128(128): {128}, - NewUInt128(200): {200}, + NewUInt128(0): {0}, + NewUInt128(42): {42}, + NewUInt128(127): {127}, + NewUInt128(128): {128}, + NewUInt128(200): {200}, + uint128LargeValueTestCase: {127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + uint128MaxValue: {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, }, "UInt256": { NewUInt256(0): {0}, @@ -593,6 +606,14 @@ func TestNumberValue_ToBigEndianBytes(t *testing.T) { NewWord64(9223372036854775808): {128, 0, 0, 0, 0, 0, 0, 0}, NewWord64(18446744073709551615): {255, 255, 255, 255, 255, 255, 255, 255}, }, + "Word128": { + NewWord128(0): {0}, + NewWord128(42): {42}, + NewWord128(127): {127}, + NewWord128(128): {128}, + word128LargeValueTestCase: {127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + word128MaxValue: {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, // Fix* "Fix64": { Fix64(0): {0, 0, 0, 0, 0, 0, 0, 0}, @@ -774,6 +795,24 @@ func TestNewUInt256FromBig(t *testing.T) { require.Error(t, err) } +func TestNewWord128FromBig(t *testing.T) { + t.Parallel() + + _, err := NewWord128FromBig(big.NewInt(1)) + require.NoError(t, err) + + belowMin := big.NewInt(-1) + _, err = NewWord128FromBig(belowMin) + require.Error(t, err) + + aboveMax := new(big.Int).Add( + sema.Word128TypeMaxIntBig, + big.NewInt(1), + ) + _, err = NewWord128FromBig(aboveMax) + require.Error(t, err) +} + func TestValue_Type(t *testing.T) { t.Parallel()