diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index 46aa3f8a6..46cd35ecf 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -24,6 +24,7 @@ import ( "encoding/hex" "errors" "fmt" + "math" "strconv" "strings" "sync" @@ -4745,7 +4746,7 @@ func TestRuntimeBlock(t *testing.T) { ) } -func TestRuntimeRandom(t *testing.T) { +func TestRuntimeRandomWithUnsafeRandom(t *testing.T) { t.Parallel() @@ -4754,7 +4755,7 @@ func TestRuntimeRandom(t *testing.T) { script := []byte(` transaction { prepare() { - let rand1 = revertibleRandom() + let rand1 = revertibleRandom() log(rand1) let rand2 = unsafeRandom() log(rand2) @@ -4796,6 +4797,93 @@ func TestRuntimeRandom(t *testing.T) { ) } +func getFixedSizeUnsignedIntegerForSemaType(ty sema.Type) cadence.Value { + switch ty { + case sema.UInt8Type: + return cadence.NewUInt8(math.MaxUint8) + case sema.UInt16Type: + return cadence.NewUInt16(math.MaxUint16) + case sema.UInt32Type: + return cadence.NewUInt32(math.MaxUint32) + case sema.UInt64Type: + return cadence.NewUInt64(math.MaxUint64) + case sema.UInt128Type: + value, _ := cadence.NewUInt128FromBig(sema.UInt128TypeMaxIntBig) + return value + case sema.UInt256Type: + value, _ := cadence.NewUInt256FromBig(sema.UInt256TypeMaxIntBig) + return value + + case sema.Word8Type: + return cadence.NewWord8(math.MaxUint8) + case sema.Word16Type: + return cadence.NewWord16(math.MaxUint16) + case sema.Word32Type: + return cadence.NewWord32(math.MaxUint32) + case sema.Word64Type: + return cadence.NewWord64(math.MaxUint64) + case sema.Word128Type: + value, _ := cadence.NewWord128FromBig(sema.Word128TypeMaxIntBig) + return value + case sema.Word256Type: + value, _ := cadence.NewWord256FromBig(sema.Word256TypeMaxIntBig) + return value + } + + panic(fmt.Sprintf("Broken test. Trying to get fixed size unsigned integer for ty: %s", ty)) +} + +func TestRuntimeRandom(t *testing.T) { + + t.Parallel() + + script := ` + pub fun main(): %[1]s { + let rand = revertibleRandom<%[1]s>() + return rand + } + ` + + runValidCaseWithoutModulo := func(t *testing.T, ty sema.Type) { + t.Run(ty.String(), func(t *testing.T) { + t.Parallel() + + nextScriptLocation := newScriptLocationGenerator() + + runtime := newTestInterpreterRuntime() + value, err := runtime.ExecuteScript( + Script{ + Source: []byte(fmt.Sprintf(script, ty.String())), + }, + Context{ + Interface: &testRuntimeInterface{ + readRandom: func(buffer []byte) error { + for i := 0; i < len(buffer); i++ { + buffer[i] = 0xff + } + return nil + }, + }, + Location: nextScriptLocation(), + }, + ) + require.NoError(t, err) + + require.Equal(t, getFixedSizeUnsignedIntegerForSemaType(ty), value) + }) + } + + for _, ty := range sema.AllFixedSizeUnsignedIntegerTypes { + switch ty { + case sema.FixedSizeUnsignedIntegerType: + continue + + default: + runValidCaseWithoutModulo(t, ty) + } + } +} + func TestRuntimeTransactionTopLevelDeclarations(t *testing.T) { t.Parallel() diff --git a/runtime/stdlib/random.go b/runtime/stdlib/random.go index 385c8c4e8..52424e608 100644 --- a/runtime/stdlib/random.go +++ b/runtime/stdlib/random.go @@ -66,11 +66,9 @@ var revertibleRandomFunctionType = func() *sema.FunctionType { type RandomGenerator interface { // ReadRandom reads pseudo-random bytes into the input slice, using distributed randomness. // The number of bytes read is equal to the length of input slice. - // TODO: Pass the modulo parameter? ReadRandom([]byte) error } -// TODO: Take modulo and pass to ReadRandom. func getRandomBytes(generator RandomGenerator, numBytes int) []byte { buffer := make([]byte, numBytes) @@ -186,7 +184,7 @@ func NewRevertibleRandomFunction(generator RandomGenerator) StandardLibraryValue return interpreter.NewWord256ValueFromBigInt( inter, func() *big.Int { - buffer := getRandomBytes(generator, 16) + buffer := getRandomBytes(generator, 32) return interpreter.LittleEndianBytesToUnsignedBigInt(buffer) }, ) diff --git a/runtime/tests/checker/builtinfunctions_test.go b/runtime/tests/checker/builtinfunctions_test.go index ec6c8a765..7df0ede09 100644 --- a/runtime/tests/checker/builtinfunctions_test.go +++ b/runtime/tests/checker/builtinfunctions_test.go @@ -27,6 +27,7 @@ import ( "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" ) func TestCheckToString(t *testing.T) { @@ -268,3 +269,86 @@ func TestCheckFromBigEndianBytes(t *testing.T) { } } } + +type testRandomGenerator struct{} + +func (*testRandomGenerator) ReadRandom([]byte) error { + return nil +} + +func TestCheckRevertibleRandom(t *testing.T) { + + t.Parallel() + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.NewRevertibleRandomFunction(&testRandomGenerator{})) + options := ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + } + + runCase := func(t *testing.T, ty sema.Type, code string) { + checker, err := ParseAndCheckWithOptions(t, code, options) + + require.NoError(t, err) + + resType := RequireGlobalValue(t, checker.Elaboration, "rand") + require.Equal(t, ty, resType) + } + + runValidCaseWithoutModulo := func(t *testing.T, ty sema.Type) { + t.Run(fmt.Sprintf("revertibleRandom<%s>_no_modulo", ty), func(t *testing.T) { + t.Parallel() + + code := fmt.Sprintf("let rand = revertibleRandom<%s>()", ty) + runCase(t, ty, code) + }) + } + + runValidCaseWithModulo := func(t *testing.T, ty sema.Type) { + t.Run(fmt.Sprintf("revertibleRandom<%s>_modulo", ty), func(t *testing.T) { + t.Parallel() + + code := fmt.Sprintf("let rand = revertibleRandom<%[1]s>(modulo: %[1]s(1))", ty) + runCase(t, ty, code) + }) + } + + runInvalidCase := func(t *testing.T, testName string, code string, expectedErrors []error) { + t.Run(testName, func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheckWithOptions(t, code, options) + + errs := RequireCheckerErrors(t, err, len(expectedErrors)) + for i := range expectedErrors { + assert.IsType(t, expectedErrors[i], errs[i]) + } + }) + } + + for _, ty := range sema.AllFixedSizeUnsignedIntegerTypes { + switch ty { + case sema.FixedSizeUnsignedIntegerType: + continue + + default: + runValidCaseWithoutModulo(t, ty) + runValidCaseWithModulo(t, ty) + } + } + + runInvalidCase(t, "revertibleRandom", "let rand = revertibleRandom()", []error{&sema.TypeMismatchError{}}) + runInvalidCase(t, "revertibleRandom", "let rand = revertibleRandom(modulo: \"abcd\")", []error{&sema.TypeMismatchError{}}) + runInvalidCase(t, "missing_argument_label", "let rand = revertibleRandom(UInt256(1))", []error{&sema.MissingArgumentLabelError{}}) + runInvalidCase(t, "incorrect_argument_label", "let rand = revertibleRandom(typo: UInt256(1))", []error{&sema.IncorrectArgumentLabelError{}}) + runInvalidCase(t, "too_many_args", "let rand = revertibleRandom(modulo: UInt256(1), 2, 3)", []error{&sema.ExcessiveArgumentsError{}}) + runInvalidCase(t, "modulo type mismatch", "let rand = revertibleRandom(modulo: UInt128(1))", []error{&sema.TypeParameterTypeMismatchError{}, &sema.TypeMismatchError{}}) + runInvalidCase(t, "string modulo", "let rand = revertibleRandom(modulo: \"abcd\")", []error{&sema.TypeParameterTypeMismatchError{}, &sema.TypeMismatchError{}}) + + // This is an error since we do not support type inference of function arguments. + runInvalidCase(t, "missing_typeinference", "let rand = revertibleRandom(modulo: 1)", []error{&sema.TypeParameterTypeMismatchError{}}) +}