diff --git a/go.mod b/go.mod index 4ee8098b0f..4b3d184b14 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.22 require ( github.com/bits-and-blooms/bitset v1.5.0 - github.com/bytecodealliance/wasmtime-go/v7 v7.0.0 github.com/c-bata/go-prompt v0.2.6 github.com/dave/dst v0.27.2 github.com/fxamacker/cbor/v2 v2.4.1-0.20230228173756-c0c9f774e40c @@ -31,6 +30,7 @@ require ( require ( github.com/SaveTheRbtz/mph v0.1.1-0.20240117162131-4166ec7869bc + github.com/bytecodealliance/wasmtime-go/v25 v25.0.0 github.com/k0kubun/pp v3.0.1+incompatible github.com/kodova/html-to-markdown v1.0.1 github.com/onflow/crypto v0.25.0 diff --git a/go.sum b/go.sum index c5711d90b7..e229bf4a00 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,8 @@ github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3 github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/bits-and-blooms/bitset v1.5.0 h1:NpE8frKRLGHIcEzkR+gZhiioW1+WbYV6fKwD6ZIpQT8= github.com/bits-and-blooms/bitset v1.5.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= -github.com/bytecodealliance/wasmtime-go/v7 v7.0.0 h1:/rBNjgFju2HCZnkPb1eL+W4GBwP8DMbaQu7i+GR9DH4= -github.com/bytecodealliance/wasmtime-go/v7 v7.0.0/go.mod h1:bu6fic7trDt20w+LMooX7j3fsOwv4/ln6j8gAdP6vmA= +github.com/bytecodealliance/wasmtime-go/v25 v25.0.0 h1:ZTn4Ho+srrk0466ugqPfTDCITczsWdT48A0ZMA/TpRU= +github.com/bytecodealliance/wasmtime-go/v25 v25.0.0/go.mod h1:8mMIYQ92CpVDwXPIb6udnhtFGI3vDZ/937cGeQr5I68= github.com/c-bata/go-prompt v0.2.6 h1:POP+nrHE+DfLYx370bedwNhsqmpCUynWPxuHi0C5vZI= github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= diff --git a/migrations/cache.go b/migrations/cache.go index e36d610edc..753d3e68ee 100644 --- a/migrations/cache.go +++ b/migrations/cache.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/runtime/account_test.go b/runtime/account_test.go index 9e2e693489..51495e3da9 100644 --- a/runtime/account_test.go +++ b/runtime/account_test.go @@ -630,10 +630,10 @@ func TestRuntimeAuthAccountKeysAdd(t *testing.T) { err := rt.ExecuteTransaction( Script{ Source: []byte(code), - Arguments: encodeArgs([]cadence.Value{ + Arguments: encodeArgs( pubKey1Value, pubKey2Value, - }), + ), }, Context{ Location: nextTransactionLocation(), @@ -1370,7 +1370,7 @@ func addPublicKeyValidation(runtimeInterface *TestRuntimeInterface, returnError } } -func encodeArgs(argValues []cadence.Value) [][]byte { +func encodeArgs(argValues ...cadence.Value) [][]byte { args := make([][]byte, len(argValues)) for i, arg := range argValues { var err error @@ -1393,7 +1393,7 @@ func (test accountKeyTestCase) executeTransaction( runtimeInterface *TestRuntimeInterface, location Location, ) error { - args := encodeArgs(test.args) + args := encodeArgs(test.args...) err := runtime.ExecuteTransaction( Script{ @@ -1413,7 +1413,7 @@ func (test accountKeyTestCase) executeScript( runtimeInterface *TestRuntimeInterface, ) (cadence.Value, error) { - args := encodeArgs(test.args) + args := encodeArgs(test.args...) value, err := runtime.ExecuteScript( Script{ diff --git a/runtime/cmd/check/main.go b/runtime/cmd/check/main.go index 69112b4aa4..0dc13099dd 100644 --- a/runtime/cmd/check/main.go +++ b/runtime/cmd/check/main.go @@ -237,7 +237,10 @@ func runPath( location := common.NewStringLocation(nil, path) // standard library handler is only needed for execution, but we're only checking - standardLibraryValues := stdlib.DefaultScriptStandardLibraryValues(nil) + standardLibraryValues := stdlib.DefaultScriptStandardLibraryValues( + nil, + stdlib.DefaultStandardLibraryOptions, + ) func() { defer func() { diff --git a/runtime/cmd/cmd.go b/runtime/cmd/cmd.go index bbd43b2397..c704b9a67c 100644 --- a/runtime/cmd/cmd.go +++ b/runtime/cmd/cmd.go @@ -179,6 +179,7 @@ func PrepareInterpreter(filename string, debugger *interpreter.Debugger) (*inter standardLibraryValues := stdlib.DefaultScriptStandardLibraryValues( &StandardLibraryHandler{}, + stdlib.DefaultStandardLibraryOptions, ) checker, must := PrepareChecker( @@ -411,6 +412,10 @@ func (*StandardLibraryHandler) BLSAggregateSignatures(_ [][]byte) ([]byte, error return nil, goerrors.New("crypto functionality is not available in this environment") } +func (*StandardLibraryHandler) CompileWebAssembly(_ []byte) (stdlib.WebAssemblyModule, error) { + return nil, goerrors.New("WebAssembly functionality is not available in this environment") +} + func (h *StandardLibraryHandler) NewOnEventEmittedHandler() interpreter.OnEventEmittedFunc { return func( inter *interpreter.Interpreter, diff --git a/runtime/cmd/compile/main.go b/runtime/cmd/compile/main.go index 192dfc5d62..bbf45c0b3f 100644 --- a/runtime/cmd/compile/main.go +++ b/runtime/cmd/compile/main.go @@ -46,7 +46,10 @@ func main() { program, must := cmd.PrepareProgramFromFile(location, codes) // standard library handler is only needed for execution, but we're only checking - standardLibraryValues := stdlib.DefaultScriptStandardLibraryValues(nil) + standardLibraryValues := stdlib.DefaultScriptStandardLibraryValues( + nil, + stdlib.DefaultStandardLibraryOptions, + ) checker, must := cmd.PrepareChecker( program, diff --git a/runtime/cmd/info/main.go b/runtime/cmd/info/main.go index fb8a56b277..a6551ca4ff 100644 --- a/runtime/cmd/info/main.go +++ b/runtime/cmd/info/main.go @@ -240,7 +240,10 @@ func dumpBuiltinValues() { } allBaseSemaValueTypes := checker.AllBaseSemaValueTypes() - standardLibraryValues := stdlib.DefaultScriptStandardLibraryValues(nil) + standardLibraryValues := stdlib.DefaultScriptStandardLibraryValues( + nil, + stdlib.DefaultStandardLibraryOptions, + ) valueTypes := make([]valueType, 0, len(allBaseSemaValueTypes)+len(standardLibraryValues)) diff --git a/runtime/common/computationkind.go b/runtime/common/computationkind.go index 3ec0e44698..ed4385d6c2 100644 --- a/runtime/common/computationkind.go +++ b/runtime/common/computationkind.go @@ -142,4 +142,8 @@ const ( // RLP ComputationKindSTDLIBRLPDecodeString ComputationKindSTDLIBRLPDecodeList + _ + _ + // WebAssembly + ComputationKindWebAssemblyFuel ) diff --git a/runtime/common/computationkind_string.go b/runtime/common/computationkind_string.go index 302fdd11bb..52f522fe14 100644 --- a/runtime/common/computationkind_string.go +++ b/runtime/common/computationkind_string.go @@ -27,6 +27,7 @@ func _() { _ = x[ComputationKindSTDLIBRevertibleRandom-1102] _ = x[ComputationKindSTDLIBRLPDecodeString-1108] _ = x[ComputationKindSTDLIBRLPDecodeList-1109] + _ = x[ComputationKindWebAssemblyFuel-1112] } const ( @@ -38,6 +39,7 @@ const ( _ComputationKind_name_5 = "EncodeValue" _ComputationKind_name_6 = "STDLIBPanicSTDLIBAssertSTDLIBRevertibleRandom" _ComputationKind_name_7 = "STDLIBRLPDecodeStringSTDLIBRLPDecodeList" + _ComputationKind_name_8 = "WebAssemblyFuel" ) var ( @@ -73,6 +75,8 @@ func (i ComputationKind) String() string { case 1108 <= i && i <= 1109: i -= 1108 return _ComputationKind_name_7[_ComputationKind_index_7[i]:_ComputationKind_index_7[i+1]] + case i == 1112: + return _ComputationKind_name_8 default: return "ComputationKind(" + strconv.FormatInt(int64(i), 10) + ")" } diff --git a/runtime/config.go b/runtime/config.go index 3e3a273ff1..f4edf4308f 100644 --- a/runtime/config.go +++ b/runtime/config.go @@ -41,4 +41,6 @@ type Config struct { LegacyContractUpgradeEnabled bool // ContractUpdateTypeRemovalEnabled specifies if type removal is enabled in contract updates ContractUpdateTypeRemovalEnabled bool + // WebAssemblyEnabled specifies if the WebAssembly API is enabled + WebAssemblyEnabled bool } diff --git a/runtime/contract_update_test.go b/runtime/contract_update_test.go index 0474b76592..8aeb56482c 100644 --- a/runtime/contract_update_test.go +++ b/runtime/contract_update_test.go @@ -385,10 +385,10 @@ func TestRuntimeContractRedeployInSameTransaction(t *testing.T) { err := runtime.ExecuteTransaction( Script{ Source: tx, - Arguments: encodeArgs([]cadence.Value{ + Arguments: encodeArgs( cadence.String(foo1), cadence.String(foo2), - }), + ), }, Context{ Interface: runtimeInterface, diff --git a/runtime/crypto_test.go b/runtime/crypto_test.go index 6cf6e1cec7..60d88d5ba6 100644 --- a/runtime/crypto_test.go +++ b/runtime/crypto_test.go @@ -358,7 +358,7 @@ func TestRuntimeSignatureAlgorithmImport(t *testing.T) { value, err := runtime.ExecuteScript( Script{ Source: []byte(script), - Arguments: encodeArgs([]cadence.Value{ + Arguments: encodeArgs( cadence.NewEnum([]cadence.Value{ cadence.UInt8(algo.RawValue()), }).WithType(cadence.NewEnumType( @@ -373,7 +373,7 @@ func TestRuntimeSignatureAlgorithmImport(t *testing.T) { }, nil, )), - }), + ), }, Context{ Interface: runtimeInterface, @@ -439,7 +439,7 @@ func TestRuntimeHashAlgorithmImport(t *testing.T) { value, err := runtime.ExecuteScript( Script{ Source: []byte(script), - Arguments: encodeArgs([]cadence.Value{ + Arguments: encodeArgs( cadence.NewEnum([]cadence.Value{ cadence.UInt8(algo.RawValue()), }).WithType(cadence.NewEnumType( @@ -454,7 +454,7 @@ func TestRuntimeHashAlgorithmImport(t *testing.T) { }, nil, )), - }), + ), }, Context{ Interface: runtimeInterface, @@ -816,7 +816,7 @@ func TestRuntimeTraversingMerkleProof(t *testing.T) { _, err := runtime.ExecuteScript( Script{ Source: script, - Arguments: encodeArgs([]cadence.Value{rootHash, address, accountProof}), + Arguments: encodeArgs(rootHash, address, accountProof), }, Context{ Interface: runtimeInterface, diff --git a/runtime/empty.go b/runtime/empty.go index 86b5b0abce..01e5f01e87 100644 --- a/runtime/empty.go +++ b/runtime/empty.go @@ -29,6 +29,7 @@ import ( "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" ) // EmptyRuntimeInterface is an empty implementation of runtime.Interface. @@ -63,6 +64,10 @@ func (EmptyRuntimeInterface) ComputationUsed() (uint64, error) { panic("unexpected call to ComputationUsed") } +func (i EmptyRuntimeInterface) ComputationRemaining(_ common.ComputationKind) uint { + panic("unexpected call to ComputationRemaining") +} + func (EmptyRuntimeInterface) MemoryUsed() (uint64, error) { panic("unexpected call to MemoryUsed") } @@ -260,3 +265,7 @@ func (EmptyRuntimeInterface) ValidateAccountCapabilitiesPublish( ) (bool, error) { panic("unexpected call to ValidateAccountCapabilitiesPublish") } + +func (EmptyRuntimeInterface) CompileWebAssembly(_ []byte) (stdlib.WebAssemblyModule, error) { + panic("unexpected call to CompileWebAssembly") +} diff --git a/runtime/environment.go b/runtime/environment.go index 825e09a8a0..4d9adbca9f 100644 --- a/runtime/environment.go +++ b/runtime/environment.go @@ -161,7 +161,9 @@ func newInterpreterEnvironment(config Config) *interpreterEnvironment { } env.InterpreterConfig = env.newInterpreterConfig() env.CheckerConfig = env.newCheckerConfig() + env.compositeValueFunctionsHandlers = stdlib.DefaultStandardLibraryCompositeValueFunctionHandlers(env) + return env } @@ -189,6 +191,7 @@ func (e *interpreterEnvironment) newInterpreterConfig() *interpreter.Config { Debugger: e.config.Debugger, OnStatement: e.newOnStatementHandler(), OnMeterComputation: e.newOnMeterComputation(), + OnComputationRemaining: e.newOnComputationRemaining(), OnFunctionInvocation: e.newOnFunctionInvocationHandler(), OnInvokedFunctionReturn: e.newOnInvokedFunctionReturnHandler(), CapabilityBorrowHandler: e.newCapabilityBorrowHandler(), @@ -213,19 +216,33 @@ func (e *interpreterEnvironment) newCheckerConfig() *sema.Config { } } +func StandardLibraryOptionsFromConfig(config Config) stdlib.StandardLibraryOptions { + return stdlib.StandardLibraryOptions{ + WebAssemblyEnabled: config.WebAssemblyEnabled, + } +} + func NewBaseInterpreterEnvironment(config Config) *interpreterEnvironment { env := newInterpreterEnvironment(config) - for _, valueDeclaration := range stdlib.DefaultStandardLibraryValues(env) { + options := StandardLibraryOptionsFromConfig(config) + for _, valueDeclaration := range stdlib.DefaultStandardLibraryValues(env, options) { env.DeclareValue(valueDeclaration, nil) } + for _, typeDeclaration := range stdlib.DefaultStandardLibraryTypes(options) { + env.DeclareType(typeDeclaration, nil) + } return env } func NewScriptInterpreterEnvironment(config Config) Environment { env := newInterpreterEnvironment(config) - for _, valueDeclaration := range stdlib.DefaultScriptStandardLibraryValues(env) { + options := StandardLibraryOptionsFromConfig(config) + for _, valueDeclaration := range stdlib.DefaultScriptStandardLibraryValues(env, options) { env.DeclareValue(valueDeclaration, nil) } + for _, typeDeclaration := range stdlib.DefaultStandardLibraryTypes(options) { + env.DeclareType(typeDeclaration, nil) + } return env } @@ -919,6 +936,10 @@ func (e *interpreterEnvironment) Hash(data []byte, tag string, algorithm sema.Ha return e.runtimeInterface.Hash(data, tag, algorithm) } +func (e *interpreterEnvironment) CompileWebAssembly(bytes []byte) (stdlib.WebAssemblyModule, error) { + return e.runtimeInterface.CompileWebAssembly(bytes) +} + func (e *interpreterEnvironment) DecodeArgument(argument []byte, argumentType cadence.Type) (cadence.Value, error) { return e.runtimeInterface.DecodeArgument(argument, argumentType) } @@ -1188,6 +1209,16 @@ func (e *interpreterEnvironment) newOnMeterComputation() interpreter.OnMeterComp } } +func (e *interpreterEnvironment) newOnComputationRemaining() interpreter.OnComputationRemainingFunc { + return func(compKind common.ComputationKind) uint { + var remaining uint + errors.WrapPanic(func() { + remaining = e.runtimeInterface.ComputationRemaining(compKind) + }) + return remaining + } +} + func (e *interpreterEnvironment) InterpretContract( location common.AddressLocation, program *interpreter.Program, diff --git a/runtime/ft_test.go b/runtime/ft_test.go index 8282108b2b..e7181ff398 100644 --- a/runtime/ft_test.go +++ b/runtime/ft_test.go @@ -688,10 +688,10 @@ func BenchmarkRuntimeFungibleTokenTransfer(b *testing.B) { err = runtime.ExecuteTransaction( Script{ Source: []byte(realMintFlowTokenTransaction), - Arguments: encodeArgs([]cadence.Value{ + Arguments: encodeArgs( cadence.Address(senderAddress), mintAmount, - }), + ), }, Context{ Interface: runtimeInterface, @@ -716,10 +716,10 @@ func BenchmarkRuntimeFungibleTokenTransfer(b *testing.B) { err = runtime.ExecuteTransaction( Script{ Source: []byte(realFlowTokenTransferTransaction), - Arguments: encodeArgs([]cadence.Value{ + Arguments: encodeArgs( sendAmount, cadence.Address(receiverAddress), - }), + ), }, Context{ Interface: runtimeInterface, @@ -748,9 +748,9 @@ func BenchmarkRuntimeFungibleTokenTransfer(b *testing.B) { result, err := runtime.ExecuteScript( Script{ Source: []byte(realFlowTokenBalanceScript), - Arguments: encodeArgs([]cadence.Value{ + Arguments: encodeArgs( cadence.Address(address), - }), + ), }, Context{ Interface: runtimeInterface, diff --git a/runtime/imported_values_memory_metering_test.go b/runtime/imported_values_memory_metering_test.go index d26059e51a..da52dd10fc 100644 --- a/runtime/imported_values_memory_metering_test.go +++ b/runtime/imported_values_memory_metering_test.go @@ -58,7 +58,7 @@ func TestRuntimeImportedValueMemoryMetering(t *testing.T) { _, err := runtime.ExecuteScript( Script{ Source: script, - Arguments: encodeArgs(args), + Arguments: encodeArgs(args...), }, Context{ Interface: runtimeInterface, @@ -545,9 +545,9 @@ func TestRuntimeImportedValueMemoryMeteringForSimpleTypes(t *testing.T) { _, err := runtime.ExecuteScript( Script{ Source: script, - Arguments: encodeArgs([]cadence.Value{ + Arguments: encodeArgs( test.TypeInstance, - }), + ), }, Context{ Interface: runtimeInterface, @@ -620,7 +620,7 @@ func TestRuntimeScriptDecodedLocationMetering(t *testing.T) { _, err := runtime.ExecuteScript( Script{ Source: script, - Arguments: encodeArgs([]cadence.Value{value}), + Arguments: encodeArgs(value), }, Context{ Interface: runtimeInterface, diff --git a/runtime/inbox_test.go b/runtime/inbox_test.go index fa11bdcdd7..667b4744f4 100644 --- a/runtime/inbox_test.go +++ b/runtime/inbox_test.go @@ -346,7 +346,7 @@ func TestRuntimeAccountInboxUnpublishRemove(t *testing.T) { nameArgument, err := cadence.NewString(strings.Repeat("x", 10_000)) require.NoError(t, err) - args := encodeArgs([]cadence.Value{nameArgument}) + args := encodeArgs(nameArgument) nextTransactionLocation := NewTransactionLocationGenerator() // publish from 1 to 2 diff --git a/runtime/interface.go b/runtime/interface.go index 9f20ba8f31..7d1153fea7 100644 --- a/runtime/interface.go +++ b/runtime/interface.go @@ -29,6 +29,7 @@ import ( "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" ) type Interface interface { @@ -161,13 +162,13 @@ type Interface interface { path interpreter.PathValue, capabilityBorrowType *interpreter.ReferenceStaticType, ) (bool, error) + CompileWebAssembly(bytes []byte) (stdlib.WebAssemblyModule, error) } type MeterInterface interface { // MeterMemory gets called when new memory is allocated or used by the interpreter MeterMemory(usage common.MemoryUsage) error - // MeterComputation is a callback method for metering computation, it returns error - // when computation passes the limit (set by the environment) + // MeterComputation gets called when a computation is performed by the interpreter. MeterComputation(operationType common.ComputationKind, intensity uint) error // ComputationUsed returns the total computation used in the current runtime. ComputationUsed() (uint64, error) @@ -175,6 +176,8 @@ type MeterInterface interface { MemoryUsed() (uint64, error) // InteractionUsed returns the total storage interaction used in the current runtime. InteractionUsed() (uint64, error) + // ComputationRemaining returns the remaining amount of computation left for the given kind. + ComputationRemaining(kind common.ComputationKind) uint } type Metrics interface { diff --git a/runtime/interpreter/config.go b/runtime/interpreter/config.go index dc052342d1..ba6462c249 100644 --- a/runtime/interpreter/config.go +++ b/runtime/interpreter/config.go @@ -35,6 +35,8 @@ type Config struct { OnResourceOwnerChange OnResourceOwnerChangeFunc // OnMeterComputation is triggered when a computation is about to happen OnMeterComputation OnMeterComputationFunc + // OnComputationRemaining is used to determine how much computation is remaining + OnComputationRemaining OnComputationRemainingFunc // InjectedCompositeFieldsHandler is used to initialize new composite values' fields InjectedCompositeFieldsHandler InjectedCompositeFieldsHandlerFunc // ContractValueHandler is used to handle imports of values diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 27b0735ea0..cf2b57934c 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -106,6 +106,11 @@ type OnMeterComputationFunc func( intensity uint, ) +// OnComputationRemainingFunc is a function that is used to determine how much computation is remaining. +type OnComputationRemainingFunc func( + kind common.ComputationKind, +) uint + // CapabilityBorrowHandlerFunc is a function that is used to borrow ID capabilities. type CapabilityBorrowHandlerFunc func( inter *Interpreter, @@ -4992,6 +4997,17 @@ func (interpreter *Interpreter) ReportComputation(compKind common.ComputationKin } } +func (interpreter *Interpreter) ComputationRemaining(compKind common.ComputationKind) uint { + config := interpreter.SharedState.Config + + onComputationRemaining := config.OnComputationRemaining + if onComputationRemaining != nil { + return onComputationRemaining(compKind) + } + + return math.MaxUint +} + func (interpreter *Interpreter) getAccessOfMember(self Value, identifier string) sema.Access { typ, err := interpreter.ConvertStaticToSemaType(self.StaticType(interpreter)) // some values (like transactions) do not have types that can be looked up this way. These types diff --git a/runtime/repl.go b/runtime/repl.go index c0dd41a687..5ac603eedf 100644 --- a/runtime/repl.go +++ b/runtime/repl.go @@ -55,7 +55,10 @@ func NewREPL() (*REPL, error) { codes := map[Location][]byte{} standardLibraryHandler := &cmd.StandardLibraryHandler{} - standardLibraryValues := stdlib.DefaultScriptStandardLibraryValues(standardLibraryHandler) + standardLibraryValues := stdlib.DefaultScriptStandardLibraryValues( + standardLibraryHandler, + stdlib.DefaultStandardLibraryOptions, + ) checkerConfig := cmd.DefaultCheckerConfig(checkers, codes, standardLibraryValues) checkerConfig.AccessCheckMode = sema.AccessCheckModeNotSpecifiedUnrestricted diff --git a/runtime/rlp_test.go b/runtime/rlp_test.go index 6784b84c35..8bb743f31f 100644 --- a/runtime/rlp_test.go +++ b/runtime/rlp_test.go @@ -135,14 +135,14 @@ func TestRuntimeRLPDecodeString(t *testing.T) { result, err := runtime.ExecuteScript( Script{ Source: script, - Arguments: encodeArgs([]cadence.Value{ + Arguments: encodeArgs( cadence.Array{ ArrayType: &cadence.VariableSizedArrayType{ ElementType: cadence.UInt8Type, }, Values: test.input, }, - }), + ), }, Context{ Interface: runtimeInterface, @@ -291,14 +291,14 @@ func TestRuntimeRLPDecodeList(t *testing.T) { result, err := runtime.ExecuteScript( Script{ Source: script, - Arguments: encodeArgs([]cadence.Value{ + Arguments: encodeArgs( cadence.Array{ ArrayType: &cadence.VariableSizedArrayType{ ElementType: cadence.UInt8Type, }, Values: test.input, }, - }), + ), }, Context{ Interface: runtimeInterface, diff --git a/runtime/runtime_memory_metering_test.go b/runtime/runtime_memory_metering_test.go index eddbbb0529..a9b94a7b99 100644 --- a/runtime/runtime_memory_metering_test.go +++ b/runtime/runtime_memory_metering_test.go @@ -225,9 +225,9 @@ func TestRuntimeCadenceValueAndTypeMetering(t *testing.T) { _, err := runtime.ExecuteScript( Script{ Source: []byte(script), - Arguments: encodeArgs([]cadence.Value{ + Arguments: encodeArgs( cadence.NewInt(12), - }), + ), }, Context{ Interface: runtimeInterface, @@ -266,9 +266,9 @@ func TestRuntimeCadenceValueAndTypeMetering(t *testing.T) { _, err := runtime.ExecuteScript( Script{ Source: []byte(script), - Arguments: encodeArgs([]cadence.Value{ + Arguments: encodeArgs( largeInt, - }), + ), }, Context{ Interface: runtimeInterface, @@ -300,9 +300,9 @@ func TestRuntimeCadenceValueAndTypeMetering(t *testing.T) { _, err := runtime.ExecuteScript( Script{ Source: []byte(script), - Arguments: encodeArgs([]cadence.Value{ + Arguments: encodeArgs( cadence.NewInt8(12), - }), + ), }, Context{ Interface: runtimeInterface, @@ -334,9 +334,9 @@ func TestRuntimeCadenceValueAndTypeMetering(t *testing.T) { _, err := runtime.ExecuteScript( Script{ Source: []byte(script), - Arguments: encodeArgs([]cadence.Value{ + Arguments: encodeArgs( cadence.NewInt16(12), - }), + ), }, Context{ Interface: runtimeInterface, @@ -368,9 +368,9 @@ func TestRuntimeCadenceValueAndTypeMetering(t *testing.T) { _, err := runtime.ExecuteScript( Script{ Source: []byte(script), - Arguments: encodeArgs([]cadence.Value{ + Arguments: encodeArgs( cadence.NewInt32(12), - }), + ), }, Context{ Interface: runtimeInterface, @@ -402,9 +402,9 @@ func TestRuntimeCadenceValueAndTypeMetering(t *testing.T) { _, err := runtime.ExecuteScript( Script{ Source: []byte(script), - Arguments: encodeArgs([]cadence.Value{ + Arguments: encodeArgs( cadence.NewInt64(12), - }), + ), }, Context{ Interface: runtimeInterface, @@ -436,9 +436,9 @@ func TestRuntimeCadenceValueAndTypeMetering(t *testing.T) { _, err := runtime.ExecuteScript( Script{ Source: []byte(script), - Arguments: encodeArgs([]cadence.Value{ + Arguments: encodeArgs( cadence.NewInt128(12), - }), + ), }, Context{ Interface: runtimeInterface, @@ -470,9 +470,9 @@ func TestRuntimeCadenceValueAndTypeMetering(t *testing.T) { _, err := runtime.ExecuteScript( Script{ Source: []byte(script), - Arguments: encodeArgs([]cadence.Value{ + Arguments: encodeArgs( cadence.NewInt256(12), - }), + ), }, Context{ Interface: runtimeInterface, @@ -953,7 +953,7 @@ func TestRuntimeMemoryMeteringErrors(t *testing.T) { _, err := runtime.ExecuteScript( Script{ Source: script, - Arguments: encodeArgs(args), + Arguments: encodeArgs(args...), }, Context{ Interface: runtimeInterface(meter), diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index d3f13fa003..3bda2bace1 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -444,9 +444,9 @@ func TestRuntimeTransactionWithArguments(t *testing.T) { } } `, - args: encodeArgs([]cadence.Value{ + args: encodeArgs( cadence.NewInt(42), - }), + ), expectedLogs: []string{"42"}, }, { @@ -462,9 +462,9 @@ func TestRuntimeTransactionWithArguments(t *testing.T) { } } `, - args: encodeArgs([]cadence.Value{ + args: encodeArgs( cadence.NewInt(42), - }), + ), authorizers: []Address{common.MustBytesToAddress([]byte{42})}, expectedLogs: []string{"0x000000000000002a", "42"}, }, @@ -478,10 +478,10 @@ func TestRuntimeTransactionWithArguments(t *testing.T) { } } `, - args: encodeArgs([]cadence.Value{ + args: encodeArgs( cadence.NewInt(42), cadence.String("foo"), - }), + ), expectedLogs: []string{"42", `"foo"`}, }, { @@ -508,9 +508,9 @@ func TestRuntimeTransactionWithArguments(t *testing.T) { } } `, - args: encodeArgs([]cadence.Value{ + args: encodeArgs( cadence.String("foo"), - }), + ), check: func(t *testing.T, err error) { RequireError(t, err) @@ -531,14 +531,14 @@ func TestRuntimeTransactionWithArguments(t *testing.T) { } } `, - args: encodeArgs([]cadence.Value{ + args: encodeArgs( cadence.BytesToAddress( []byte{ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, }, ), - }), + ), expectedLogs: []string{"0x0000000000000001"}, }, { @@ -550,7 +550,7 @@ func TestRuntimeTransactionWithArguments(t *testing.T) { } } `, - args: encodeArgs([]cadence.Value{ + args: encodeArgs( cadence.NewArray( []cadence.Value{ cadence.NewInt(1), @@ -558,7 +558,7 @@ func TestRuntimeTransactionWithArguments(t *testing.T) { cadence.NewInt(3), }, ), - }), + ), expectedLogs: []string{"[1, 2, 3]"}, }, { @@ -570,7 +570,7 @@ func TestRuntimeTransactionWithArguments(t *testing.T) { } } `, - args: encodeArgs([]cadence.Value{ + args: encodeArgs( cadence.NewDictionary( []cadence.KeyValuePair{ { @@ -579,7 +579,7 @@ func TestRuntimeTransactionWithArguments(t *testing.T) { }, }, ), - }), + ), expectedLogs: []string{"42"}, }, { @@ -591,7 +591,7 @@ func TestRuntimeTransactionWithArguments(t *testing.T) { } } `, - args: encodeArgs([]cadence.Value{ + args: encodeArgs( cadence.NewDictionary( []cadence.KeyValuePair{ { @@ -600,7 +600,7 @@ func TestRuntimeTransactionWithArguments(t *testing.T) { }, }, ), - }), + ), check: func(t *testing.T, err error) { RequireError(t, err) @@ -637,7 +637,7 @@ func TestRuntimeTransactionWithArguments(t *testing.T) { } } `, - args: encodeArgs([]cadence.Value{ + args: encodeArgs( cadence. NewStruct([]cadence.Value{cadence.String("bar")}). WithType(cadence.NewStructType( @@ -654,7 +654,7 @@ func TestRuntimeTransactionWithArguments(t *testing.T) { }, nil, )), - }), + ), expectedLogs: []string{`"bar"`}, }, { @@ -685,7 +685,7 @@ func TestRuntimeTransactionWithArguments(t *testing.T) { } } `, - args: encodeArgs([]cadence.Value{ + args: encodeArgs( cadence.NewArray([]cadence.Value{ cadence. NewStruct([]cadence.Value{cadence.String("bar")}). @@ -704,7 +704,7 @@ func TestRuntimeTransactionWithArguments(t *testing.T) { nil, )), }), - }), + ), expectedLogs: []string{`"bar"`}, }, } @@ -819,9 +819,9 @@ func TestRuntimeScriptArguments(t *testing.T) { log(x) } `, - args: encodeArgs([]cadence.Value{ + args: encodeArgs( cadence.NewInt(42), - }), + ), expectedLogs: []string{"42"}, }, { @@ -832,10 +832,10 @@ func TestRuntimeScriptArguments(t *testing.T) { log(y) } `, - args: encodeArgs([]cadence.Value{ + args: encodeArgs( cadence.NewInt(42), cadence.String("foo"), - }), + ), expectedLogs: []string{"42", `"foo"`}, }, { @@ -861,9 +861,9 @@ func TestRuntimeScriptArguments(t *testing.T) { log(x) } `, - args: encodeArgs([]cadence.Value{ + args: encodeArgs( cadence.String("foo"), - }), + ), check: func(t *testing.T, err error) { RequireError(t, err) @@ -880,14 +880,14 @@ func TestRuntimeScriptArguments(t *testing.T) { log(x) } `, - args: encodeArgs([]cadence.Value{ + args: encodeArgs( cadence.BytesToAddress( []byte{ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, }, ), - }), + ), expectedLogs: []string{"0x0000000000000001"}, }, { @@ -897,7 +897,7 @@ func TestRuntimeScriptArguments(t *testing.T) { log(x) } `, - args: encodeArgs([]cadence.Value{ + args: encodeArgs( cadence.NewArray( []cadence.Value{ cadence.NewInt(1), @@ -905,7 +905,7 @@ func TestRuntimeScriptArguments(t *testing.T) { cadence.NewInt(3), }, ), - }), + ), expectedLogs: []string{"[1, 2, 3]"}, }, { @@ -915,7 +915,7 @@ func TestRuntimeScriptArguments(t *testing.T) { log(x) } `, - args: encodeArgs([]cadence.Value{ + args: encodeArgs( cadence.NewArray( []cadence.Value{ cadence.NewInt(1), @@ -923,7 +923,7 @@ func TestRuntimeScriptArguments(t *testing.T) { cadence.NewInt(3), }, ), - }), + ), check: func(t *testing.T, err error) { RequireError(t, err) @@ -940,13 +940,13 @@ func TestRuntimeScriptArguments(t *testing.T) { log(x) } `, - args: encodeArgs([]cadence.Value{ + args: encodeArgs( cadence.NewArray( []cadence.Value{ cadence.NewInt(1), }, ), - }), + ), check: func(t *testing.T, err error) { RequireError(t, err) @@ -963,7 +963,7 @@ func TestRuntimeScriptArguments(t *testing.T) { log(x["y"]) } `, - args: encodeArgs([]cadence.Value{ + args: encodeArgs( cadence.NewDictionary( []cadence.KeyValuePair{ { @@ -972,7 +972,7 @@ func TestRuntimeScriptArguments(t *testing.T) { }, }, ), - }), + ), expectedLogs: []string{"42"}, }, { @@ -982,7 +982,7 @@ func TestRuntimeScriptArguments(t *testing.T) { log(x["y"]) } `, - args: encodeArgs([]cadence.Value{ + args: encodeArgs( cadence.NewDictionary( []cadence.KeyValuePair{ { @@ -991,7 +991,7 @@ func TestRuntimeScriptArguments(t *testing.T) { }, }, ), - }), + ), check: func(t *testing.T, err error) { RequireError(t, err) @@ -1016,7 +1016,7 @@ func TestRuntimeScriptArguments(t *testing.T) { log(x.y) } `, - args: encodeArgs([]cadence.Value{ + args: encodeArgs( cadence. NewStruct([]cadence.Value{cadence.String("bar")}). WithType(cadence.NewStructType( @@ -1030,7 +1030,7 @@ func TestRuntimeScriptArguments(t *testing.T) { }, nil, )), - }), + ), expectedLogs: []string{`"bar"`}, }, { @@ -1049,7 +1049,7 @@ func TestRuntimeScriptArguments(t *testing.T) { log(x.y) } `, - args: encodeArgs([]cadence.Value{ + args: encodeArgs( cadence.NewArray([]cadence.Value{ cadence. NewStruct([]cadence.Value{cadence.String("bar")}). @@ -1065,7 +1065,7 @@ func TestRuntimeScriptArguments(t *testing.T) { nil, )), }), - }), + ), expectedLogs: []string{`"bar"`}, }, { @@ -1075,12 +1075,12 @@ func TestRuntimeScriptArguments(t *testing.T) { log(x) } `, - args: encodeArgs([]cadence.Value{ + args: encodeArgs( cadence.Path{ Domain: common.PathDomainStorage, Identifier: "foo", }, - }), + ), expectedLogs: []string{ "/storage/foo", }, @@ -6955,17 +6955,17 @@ func TestRuntimeExecuteScriptArguments(t *testing.T) { }, { name: "correct number of arguments", - arguments: encodeArgs([]cadence.Value{ + arguments: encodeArgs( cadence.NewInt(1), - }), + ), valid: true, }, { name: "too many arguments", - arguments: encodeArgs([]cadence.Value{ + arguments: encodeArgs( cadence.NewInt(1), cadence.NewInt(2), - }), + ), valid: false, }, } { @@ -8057,9 +8057,9 @@ func TestRuntimeAccountTypeEquality(t *testing.T) { result, err := rt.ExecuteScript( Script{ Source: script, - Arguments: encodeArgs([]cadence.Value{ + Arguments: encodeArgs( cadence.Address(common.MustBytesToAddress([]byte{0x1})), - }), + ), }, Context{ Interface: runtimeInterface, @@ -9304,9 +9304,9 @@ func TestRuntimeInvalidWrappedPrivateCapability(t *testing.T) { capability, err := runtime.ExecuteScript( Script{ Source: getCapScript, - Arguments: encodeArgs([]cadence.Value{ + Arguments: encodeArgs( cadence.BytesToAddress([]byte{0x1}), - }), + ), }, Context{ Interface: runtimeInterface, @@ -9320,9 +9320,9 @@ func TestRuntimeInvalidWrappedPrivateCapability(t *testing.T) { err = runtime.ExecuteTransaction( Script{ Source: attackTx, - Arguments: encodeArgs([]cadence.Value{ + Arguments: encodeArgs( capability, - }), + ), }, Context{ Interface: runtimeInterface, diff --git a/runtime/stdlib/builtin.go b/runtime/stdlib/builtin.go index afc9662f24..49be680dac 100644 --- a/runtime/stdlib/builtin.go +++ b/runtime/stdlib/builtin.go @@ -36,10 +36,32 @@ type StandardLibraryHandler interface { BLSPublicKeyAggregator BLSSignatureAggregator Hasher + WebAssemblyContractHandler } -func DefaultStandardLibraryValues(handler StandardLibraryHandler) []StandardLibraryValue { - return []StandardLibraryValue{ +type StandardLibraryOptions struct { + WebAssemblyEnabled bool +} + +var DefaultStandardLibraryOptions = StandardLibraryOptions{ + WebAssemblyEnabled: false, +} + +func DefaultStandardLibraryValues( + handler StandardLibraryHandler, + options StandardLibraryOptions, +) []StandardLibraryValue { + var values []StandardLibraryValue + + if options.WebAssemblyEnabled { + values = append( + values, + NewWebAssemblyContract(nil, handler), + ) + } + + return append( + values, AssertFunction, PanicFunction, SignatureAlgorithmConstructor, @@ -54,16 +76,34 @@ func DefaultStandardLibraryValues(handler StandardLibraryHandler) []StandardLibr NewPublicKeyConstructor(handler), NewBLSContract(nil, handler), NewHashAlgorithmConstructor(handler), - } + ) } -func DefaultScriptStandardLibraryValues(handler StandardLibraryHandler) []StandardLibraryValue { +func DefaultScriptStandardLibraryValues( + handler StandardLibraryHandler, + options StandardLibraryOptions, +) []StandardLibraryValue { return append( - DefaultStandardLibraryValues(handler), + DefaultStandardLibraryValues(handler, options), NewGetAuthAccountFunction(handler), ) } +func DefaultStandardLibraryTypes( + options StandardLibraryOptions, +) []StandardLibraryType { + var types []StandardLibraryType + + if options.WebAssemblyEnabled { + types = append( + types, + WebAssemblyContractType, + ) + } + + return types +} + type CompositeValueFunctionsHandler func( inter *interpreter.Interpreter, locationRange interpreter.LocationRange, diff --git a/runtime/stdlib/webassembly.cdc b/runtime/stdlib/webassembly.cdc new file mode 100644 index 0000000000..574d974298 --- /dev/null +++ b/runtime/stdlib/webassembly.cdc @@ -0,0 +1,27 @@ +access(all) +contract WebAssembly { + + /// Compile WebAssembly binary code into a Module and instantiate it. + /// Imports are not supported. + access(all) + view fun compileAndInstantiate(bytes: [UInt8]): &WebAssembly.InstantiatedSource + + access(all) + struct InstantiatedSource { + + /// The instance. + access(all) + let instance: &WebAssembly.Instance + } + + access(all) + struct Instance { + + /// Get the exported value. + /// The type must match the type of the exported value. + /// If the export with the given name does not exist, + /// of if the type does not match, then the function will panic. + access(all) + view fun getExport(name: String): T + } +} diff --git a/runtime/stdlib/webassembly.gen.go b/runtime/stdlib/webassembly.gen.go new file mode 100644 index 0000000000..8dc51ddf4a --- /dev/null +++ b/runtime/stdlib/webassembly.gen.go @@ -0,0 +1,181 @@ +// Code generated from webassembly.cdc. DO NOT EDIT. +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package stdlib + +import ( + "github.com/onflow/cadence/runtime/ast" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/sema" +) + +const WebAssemblyTypeCompileAndInstantiateFunctionName = "compileAndInstantiate" + +var WebAssemblyTypeCompileAndInstantiateFunctionType = &sema.FunctionType{ + Purity: sema.FunctionPurityView, + Parameters: []sema.Parameter{ + { + Identifier: "bytes", + TypeAnnotation: sema.NewTypeAnnotation(&sema.VariableSizedType{ + Type: sema.UInt8Type, + }), + }, + }, + ReturnTypeAnnotation: sema.NewTypeAnnotation( + &sema.ReferenceType{ + Type: WebAssembly_InstantiatedSourceType, + Authorization: sema.UnauthorizedAccess, + }, + ), +} + +const WebAssemblyTypeCompileAndInstantiateFunctionDocString = ` +Compile WebAssembly binary code into a Module and instantiate it. +Imports are not supported. +` + +const WebAssembly_InstantiatedSourceTypeInstanceFieldName = "instance" + +var WebAssembly_InstantiatedSourceTypeInstanceFieldType = &sema.ReferenceType{ + Type: WebAssembly_InstanceType, + Authorization: sema.UnauthorizedAccess, +} + +const WebAssembly_InstantiatedSourceTypeInstanceFieldDocString = ` +The instance. +` + +const WebAssembly_InstantiatedSourceTypeName = "InstantiatedSource" + +var WebAssembly_InstantiatedSourceType = func() *sema.CompositeType { + var t = &sema.CompositeType{ + Identifier: WebAssembly_InstantiatedSourceTypeName, + Kind: common.CompositeKindStructure, + ImportableBuiltin: false, + HasComputedMembers: true, + } + + return t +}() + +func init() { + var members = []*sema.Member{ + sema.NewUnmeteredFieldMember( + WebAssembly_InstantiatedSourceType, + sema.PrimitiveAccess(ast.AccessAll), + ast.VariableKindConstant, + WebAssembly_InstantiatedSourceTypeInstanceFieldName, + WebAssembly_InstantiatedSourceTypeInstanceFieldType, + WebAssembly_InstantiatedSourceTypeInstanceFieldDocString, + ), + } + + WebAssembly_InstantiatedSourceType.Members = sema.MembersAsMap(members) + WebAssembly_InstantiatedSourceType.Fields = sema.MembersFieldNames(members) +} + +const WebAssembly_InstanceTypeGetExportFunctionName = "getExport" + +var WebAssembly_InstanceTypeGetExportFunctionTypeParameterT = &sema.TypeParameter{ + Name: "T", + TypeBound: sema.AnyStructType, +} + +var WebAssembly_InstanceTypeGetExportFunctionType = &sema.FunctionType{ + Purity: sema.FunctionPurityView, + TypeParameters: []*sema.TypeParameter{ + WebAssembly_InstanceTypeGetExportFunctionTypeParameterT, + }, + Parameters: []sema.Parameter{ + { + Identifier: "name", + TypeAnnotation: sema.NewTypeAnnotation(sema.StringType), + }, + }, + ReturnTypeAnnotation: sema.NewTypeAnnotation( + &sema.GenericType{ + TypeParameter: WebAssembly_InstanceTypeGetExportFunctionTypeParameterT, + }, + ), +} + +const WebAssembly_InstanceTypeGetExportFunctionDocString = ` +Get the exported value. +The type must match the type of the exported value. +If the export with the given name does not exist, +of if the type does not match, then the function will panic. +` + +const WebAssembly_InstanceTypeName = "Instance" + +var WebAssembly_InstanceType = func() *sema.CompositeType { + var t = &sema.CompositeType{ + Identifier: WebAssembly_InstanceTypeName, + Kind: common.CompositeKindStructure, + ImportableBuiltin: false, + HasComputedMembers: true, + } + + return t +}() + +func init() { + var members = []*sema.Member{ + sema.NewUnmeteredFunctionMember( + WebAssembly_InstanceType, + sema.PrimitiveAccess(ast.AccessAll), + WebAssembly_InstanceTypeGetExportFunctionName, + WebAssembly_InstanceTypeGetExportFunctionType, + WebAssembly_InstanceTypeGetExportFunctionDocString, + ), + } + + WebAssembly_InstanceType.Members = sema.MembersAsMap(members) + WebAssembly_InstanceType.Fields = sema.MembersFieldNames(members) +} + +const WebAssemblyTypeName = "WebAssembly" + +var WebAssemblyType = func() *sema.CompositeType { + var t = &sema.CompositeType{ + Identifier: WebAssemblyTypeName, + Kind: common.CompositeKindContract, + ImportableBuiltin: false, + HasComputedMembers: true, + } + + t.SetNestedType(WebAssembly_InstantiatedSourceTypeName, WebAssembly_InstantiatedSourceType) + t.SetNestedType(WebAssembly_InstanceTypeName, WebAssembly_InstanceType) + return t +}() + +func init() { + var members = []*sema.Member{ + sema.NewUnmeteredFunctionMember( + WebAssemblyType, + sema.PrimitiveAccess(ast.AccessAll), + WebAssemblyTypeCompileAndInstantiateFunctionName, + WebAssemblyTypeCompileAndInstantiateFunctionType, + WebAssemblyTypeCompileAndInstantiateFunctionDocString, + ), + } + + WebAssemblyType.Members = sema.MembersAsMap(members) + WebAssemblyType.Fields = sema.MembersFieldNames(members) +} diff --git a/runtime/stdlib/webassembly.go b/runtime/stdlib/webassembly.go new file mode 100644 index 0000000000..1e587e2650 --- /dev/null +++ b/runtime/stdlib/webassembly.go @@ -0,0 +1,261 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package stdlib + +import ( + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" +) + +//go:generate go run ../sema/gen -p stdlib webassembly.cdc webassembly.gen.go + +func newWebAssemblyCompileAndInstantiateFunction( + gauge common.MemoryGauge, + handler WebAssemblyContractHandler, +) *interpreter.HostFunctionValue { + return interpreter.NewStaticHostFunctionValue( + gauge, + WebAssemblyTypeCompileAndInstantiateFunctionType, + func(invocation interpreter.Invocation) interpreter.Value { + inter := invocation.Interpreter + locationRange := invocation.LocationRange + + bytesValue := invocation.Arguments[0] + bytes, err := interpreter.ByteArrayValueToByteSlice(inter, bytesValue, locationRange) + if err != nil { + panic(err) + } + + // TODO meter + + module, err := handler.CompileWebAssembly(bytes) + if err != nil { + panic(err) + } + + instance, err := module.InstantiateWebAssemblyModule(gauge) + if err != nil { + panic(err) + } + + instanceValue := NewWebAssemblyInstanceValue(gauge, instance) + instanceReferenceValue := interpreter.NewEphemeralReferenceValue( + inter, + interpreter.UnauthorizedAccess, + instanceValue, + WebAssembly_InstanceType, + locationRange, + ) + + instantiatedSourceValue := NewWebAssemblyInstantiatedSourceValue(gauge, instanceReferenceValue) + return interpreter.NewEphemeralReferenceValue( + inter, + interpreter.UnauthorizedAccess, + instantiatedSourceValue, + WebAssembly_InstantiatedSourceType, + locationRange, + ) + }, + ) +} + +func newWebAssemblyInstanceGetExportFunction( + gauge common.MemoryGauge, + instance WebAssemblyInstance, +) *interpreter.HostFunctionValue { + // TODO: make bound + return interpreter.NewStaticHostFunctionValue( + gauge, + WebAssembly_InstanceTypeGetExportFunctionType, + func(invocation interpreter.Invocation) interpreter.Value { + inter := invocation.Interpreter + locationRange := invocation.LocationRange + + // Name + nameValue, ok := invocation.Arguments[0].(*interpreter.StringValue) + if !ok { + panic(errors.NewUnreachableError()) + } + name := nameValue.Str + + // Type + typeParameterPair := invocation.TypeParameterTypes.Oldest() + if typeParameterPair == nil { + panic(errors.NewUnreachableError()) + } + ty := typeParameterPair.Value + + // Get and check export + export, err := instance.GetExport(inter, name) + if err != nil { + panic(err) + } + if export == nil { + // TODO: improve error + panic(errors.NewDefaultUserError( + "WebAssembly module does not have an export with name '%s'", + name, + )) + } + + if !sema.IsSubType(export.Type, ty) { + panic(interpreter.TypeMismatchError{ + ExpectedType: ty, + ActualType: export.Type, + LocationRange: locationRange, + }) + } + + return export.Value + }, + ) +} + +var webAssembly_InstanceStaticType = interpreter.ConvertSemaCompositeTypeToStaticCompositeType( + nil, + WebAssembly_InstanceType, +) + +func NewWebAssemblyInstanceValue( + gauge common.MemoryGauge, + instance WebAssemblyInstance, +) *interpreter.SimpleCompositeValue { + return interpreter.NewSimpleCompositeValue( + gauge, + WebAssembly_InstanceType.ID(), + webAssembly_InstanceStaticType, + WebAssembly_InstanceType.Fields, + map[string]interpreter.Value{ + WebAssembly_InstanceTypeGetExportFunctionName: newWebAssemblyInstanceGetExportFunction(gauge, instance), + }, + nil, + nil, + nil, + ) +} + +var webAssembly_InstantiatedSourceStaticType = interpreter.ConvertSemaCompositeTypeToStaticCompositeType( + nil, + WebAssembly_InstantiatedSourceType, +) + +func NewWebAssemblyInstantiatedSourceValue( + gauge common.MemoryGauge, + instanceValue interpreter.Value, +) *interpreter.SimpleCompositeValue { + return interpreter.NewSimpleCompositeValue( + gauge, + WebAssembly_InstantiatedSourceType.ID(), + webAssembly_InstantiatedSourceStaticType, + WebAssembly_InstantiatedSourceType.Fields, + map[string]interpreter.Value{ + WebAssembly_InstantiatedSourceTypeInstanceFieldName: instanceValue, + }, + nil, + nil, + nil, + ) +} + +type WebAssemblyModule interface { + InstantiateWebAssemblyModule(gauge common.MemoryGauge) (WebAssemblyInstance, error) +} + +type WebAssemblyInstance interface { + GetExport(gauge common.MemoryGauge, name string) (*WebAssemblyExport, error) +} + +type WebAssemblyExport struct { + Type sema.Type + Value interpreter.Value +} + +type WebAssemblyContractHandler interface { + CompileWebAssembly(bytes []byte) (WebAssemblyModule, error) +} + +var WebAssemblyTypeStaticType = interpreter.ConvertSemaToStaticType(nil, WebAssemblyType) + +func NewWebAssemblyContract( + gauge common.MemoryGauge, + handler WebAssemblyContractHandler, +) StandardLibraryValue { + webAssemblyContractFields := map[string]interpreter.Value{ + WebAssemblyTypeCompileAndInstantiateFunctionName: newWebAssemblyCompileAndInstantiateFunction(gauge, handler), + } + + webAssemblyContractValue := interpreter.NewSimpleCompositeValue( + gauge, + WebAssemblyType.ID(), + WebAssemblyTypeStaticType, + nil, + webAssemblyContractFields, + nil, + nil, + nil, + ) + + return StandardLibraryValue{ + Name: WebAssemblyTypeName, + Type: WebAssemblyType, + Value: webAssemblyContractValue, + Kind: common.DeclarationKindContract, + } +} + +var WebAssemblyContractType = StandardLibraryType{ + Name: WebAssemblyTypeName, + Type: WebAssemblyType, + Kind: common.DeclarationKindContract, +} + +type WebAssemblyTrapError struct{} + +var _ error = WebAssemblyTrapError{} +var _ errors.UserError = WebAssemblyTrapError{} + +func (WebAssemblyTrapError) IsUserError() {} + +func (WebAssemblyTrapError) Error() string { + return "WebAssembly trap" +} + +type WebAssemblyNonFunctionExportError struct{} + +var _ error = WebAssemblyNonFunctionExportError{} +var _ errors.UserError = WebAssemblyNonFunctionExportError{} + +func (WebAssemblyNonFunctionExportError) IsUserError() {} + +func (WebAssemblyNonFunctionExportError) Error() string { + return "invalid WebAssembly export: not a function" +} + +type WebAssemblyCompilationError struct{} + +var _ error = WebAssemblyCompilationError{} +var _ errors.UserError = WebAssemblyCompilationError{} + +func (WebAssemblyCompilationError) IsUserError() {} + +func (WebAssemblyCompilationError) Error() string { + return "invalid WebAssembly module" +} diff --git a/runtime/tests/checker/crypto_test.go b/runtime/tests/checker/crypto_test.go index 1e3764c060..2569c8fe67 100644 --- a/runtime/tests/checker/crypto_test.go +++ b/runtime/tests/checker/crypto_test.go @@ -34,7 +34,10 @@ func TestCheckHashAlgorithmCases(t *testing.T) { t.Parallel() baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) - for _, value := range stdlib.DefaultScriptStandardLibraryValues(nil) { + for _, value := range stdlib.DefaultScriptStandardLibraryValues( + nil, + stdlib.DefaultStandardLibraryOptions, + ) { baseValueActivation.DeclareValue(value) } @@ -173,7 +176,10 @@ func TestCheckVerifyPoP(t *testing.T) { t.Parallel() baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) - for _, valueDeclaration := range stdlib.DefaultStandardLibraryValues(nil) { + for _, valueDeclaration := range stdlib.DefaultStandardLibraryValues( + nil, + stdlib.DefaultStandardLibraryOptions, + ) { baseValueActivation.DeclareValue(valueDeclaration) } @@ -202,7 +208,10 @@ func TestCheckVerifyPoPInvalidArgument(t *testing.T) { t.Parallel() baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) - for _, valueDeclaration := range stdlib.DefaultStandardLibraryValues(nil) { + for _, valueDeclaration := range stdlib.DefaultStandardLibraryValues( + nil, + stdlib.DefaultStandardLibraryOptions, + ) { baseValueActivation.DeclareValue(valueDeclaration) } @@ -283,7 +292,10 @@ func TestCheckBLSAggregatePublicKeys(t *testing.T) { t.Parallel() baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) - for _, valueDeclaration := range stdlib.DefaultStandardLibraryValues(nil) { + for _, valueDeclaration := range stdlib.DefaultStandardLibraryValues( + nil, + stdlib.DefaultStandardLibraryOptions, + ) { baseValueActivation.DeclareValue(valueDeclaration) } @@ -313,7 +325,10 @@ func TestCheckInvalidBLSAggregatePublicKeys(t *testing.T) { t.Parallel() baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) - for _, valueDeclaration := range stdlib.DefaultStandardLibraryValues(nil) { + for _, valueDeclaration := range stdlib.DefaultStandardLibraryValues( + nil, + stdlib.DefaultStandardLibraryOptions, + ) { baseValueActivation.DeclareValue(valueDeclaration) } diff --git a/runtime/tests/interpreter/memory_metering_test.go b/runtime/tests/interpreter/memory_metering_test.go index 43a2f77887..e2a6a9b844 100644 --- a/runtime/tests/interpreter/memory_metering_test.go +++ b/runtime/tests/interpreter/memory_metering_test.go @@ -1033,7 +1033,10 @@ func TestInterpretHostFunctionMetering(t *testing.T) { baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) baseActivation := activations.NewActivation(nil, interpreter.BaseActivation) - for _, valueDeclaration := range stdlib.DefaultStandardLibraryValues(nil) { + for _, valueDeclaration := range stdlib.DefaultStandardLibraryValues( + nil, + stdlib.DefaultStandardLibraryOptions, + ) { baseValueActivation.DeclareValue(valueDeclaration) interpreter.Declare(baseActivation, valueDeclaration) } diff --git a/runtime/tests/runtime_utils/testinterface.go b/runtime/tests/runtime_utils/testinterface.go index e387075c54..6467e396e7 100644 --- a/runtime/tests/runtime_utils/testinterface.go +++ b/runtime/tests/runtime_utils/testinterface.go @@ -22,6 +22,7 @@ import ( "bytes" "encoding/binary" "errors" + "math" "time" "github.com/onflow/atree" @@ -120,6 +121,7 @@ type TestRuntimeInterface struct { OnComputationUsed func() (uint64, error) OnMemoryUsed func() (uint64, error) OnInteractionUsed func() (uint64, error) + OnComputationRemaining func(kind common.ComputationKind) uint OnGenerateAccountID func(address common.Address) (uint64, error) OnRecoverProgram func(program *ast.Program, location common.Location) ([]byte, error) OnValidateAccountCapabilitiesGet func( @@ -137,6 +139,7 @@ type TestRuntimeInterface struct { path interpreter.PathValue, capabilityBorrowType *interpreter.ReferenceStaticType, ) (bool, error) + OnCompileWebAssembly func(bytes []byte) (stdlib.WebAssemblyModule, error) lastUUID uint64 accountIDs map[common.Address]uint64 @@ -561,6 +564,14 @@ func (i *TestRuntimeInterface) GenerateAccountID(address common.Address) (uint64 return i.OnGenerateAccountID(address) } +func (i *TestRuntimeInterface) CompileWebAssembly(bytes []byte) (stdlib.WebAssemblyModule, error) { + if i.OnCompileWebAssembly == nil { + return nil, nil + } + + return i.OnCompileWebAssembly(bytes) +} + func (i *TestRuntimeInterface) RecordTrace( operation string, location runtime.Location, @@ -605,6 +616,13 @@ func (i *TestRuntimeInterface) InteractionUsed() (uint64, error) { return i.OnInteractionUsed() } +func (i *TestRuntimeInterface) ComputationRemaining(kind common.ComputationKind) uint { + if i.OnComputationRemaining == nil { + return math.MaxUint + } + return i.OnComputationRemaining(kind) +} + func (i *TestRuntimeInterface) onTransactionExecutionStart() { i.InvalidateUpdatedPrograms() } diff --git a/runtime/validation_test.go b/runtime/validation_test.go index bb38c9d5c7..951907b277 100644 --- a/runtime/validation_test.go +++ b/runtime/validation_test.go @@ -65,7 +65,7 @@ func TestRuntimeArgumentImportMissingType(t *testing.T) { err := runtime.ExecuteTransaction( Script{ Source: script, - Arguments: encodeArgs([]cadence.Value{ + Arguments: encodeArgs( cadence.Struct{}. WithType(&cadence.StructType{ Location: common.AddressLocation{ @@ -74,7 +74,7 @@ func TestRuntimeArgumentImportMissingType(t *testing.T) { }, QualifiedIdentifier: "Foo.Bar", }), - }), + ), }, Context{ Interface: runtimeInterface, @@ -112,7 +112,7 @@ func TestRuntimeArgumentImportMissingType(t *testing.T) { _, err := runtime.ExecuteScript( Script{ Source: script, - Arguments: encodeArgs([]cadence.Value{ + Arguments: encodeArgs( cadence.Struct{}. WithType(&cadence.StructType{ Location: common.AddressLocation{ @@ -121,7 +121,7 @@ func TestRuntimeArgumentImportMissingType(t *testing.T) { }, QualifiedIdentifier: "Foo.Bar", }), - }), + ), }, Context{ Interface: runtimeInterface, diff --git a/runtime/webassembly.go b/runtime/webassembly.go new file mode 100644 index 0000000000..c67745264b --- /dev/null +++ b/runtime/webassembly.go @@ -0,0 +1,307 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package runtime + +import ( + "fmt" + + "github.com/bytecodealliance/wasmtime-go/v25" + + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" +) + +type WasmtimeWebAssemblyModule struct { + Module *wasmtime.Module + Store *wasmtime.Store +} + +func NewWasmtimeWebAssemblyModule(bytes []byte) (stdlib.WebAssemblyModule, error) { + config := wasmtime.NewConfig() + + config.SetConsumeFuel(true) + + // TODO: define max stack size + const todoMaxStackSize = 512 * 1024 + config.SetMaxWasmStack(todoMaxStackSize) + + // Enable bulk memory operations, + // programs can use them to operate on memory more efficiently. + config.SetWasmBulkMemory(true) + + // Deterministic configuration inspired by + // https://github.com/dfinity/ic/blob/a0ab22537bdf65bd1f473654d49283e4f95f5a61/rs/embedders/README.adoc#nondeterminism + + config.SetWasmThreads(false) + config.SetWasmSIMD(false) + config.SetWasmRelaxedSIMD(false) + config.SetWasmRelaxedSIMDDeterministic(false) + + config.SetStrategy(wasmtime.StrategyCranelift) + config.SetCraneliftFlag("enable_nan_canonicalization", "true") + + // Disable optimizations to keep compilation simple and fast. + config.SetCraneliftOptLevel(wasmtime.OptLevelNone) + + // Disable other features for now, + // maybe consider enabling them later. + config.SetWasmReferenceTypes(false) + config.SetWasmMemory64(false) + config.SetWasmMultiMemory(false) + config.SetWasmMultiValue(false) + + engine := wasmtime.NewEngineWithConfig(config) + + store := wasmtime.NewStore(engine) + + // TODO: define memory limit + const todoMemoryLimit = 2 * 1024 * 1024 + // TODO: define table elements limit + const tableElementsLimit = 10_000 + // TODO: define tables limit + const todoTablesLimit = 1 + // TODO: define memories limit + const todoMemoriesLimit = 1 + store.Limiter( + todoMemoryLimit, + tableElementsLimit, + -1, + todoTablesLimit, + todoMemoriesLimit, + ) + + module, err := wasmtime.NewModule(engine, bytes) + if err != nil { + if _, ok := err.(*wasmtime.Error); ok { + return nil, stdlib.WebAssemblyCompilationError{} + } + + panic(errors.NewUnexpectedError("WebAssembly module compilation failed with unknown error")) + } + + return WasmtimeWebAssemblyModule{ + Store: store, + Module: module, + }, nil +} + +var _ stdlib.WebAssemblyModule = WasmtimeWebAssemblyModule{} + +func (m WasmtimeWebAssemblyModule) InstantiateWebAssemblyModule(_ common.MemoryGauge) (stdlib.WebAssemblyInstance, error) { + instance, err := wasmtime.NewInstance(m.Store, m.Module, nil) + if err != nil { + if _, ok := err.(*wasmtime.Trap); ok { + return nil, stdlib.WebAssemblyTrapError{} + } + + panic(errors.NewUnexpectedError("WebAssembly module instantiation failed with unknown error")) + } + return WasmtimeWebAssemblyInstance{ + Instance: instance, + Store: m.Store, + }, nil +} + +type WasmtimeWebAssemblyInstance struct { + Instance *wasmtime.Instance + Store *wasmtime.Store +} + +var _ stdlib.WebAssemblyInstance = WasmtimeWebAssemblyInstance{} + +func wasmtimeValKindToSemaType(valKind wasmtime.ValKind) sema.Type { + switch valKind { + case wasmtime.KindI32: + return sema.Int32Type + + case wasmtime.KindI64: + return sema.Int64Type + + default: + return nil + } +} + +func (i WasmtimeWebAssemblyInstance) GetExport(gauge common.MemoryGauge, name string) (*stdlib.WebAssemblyExport, error) { + extern := i.Instance.GetExport(i.Store, name) + if extern == nil { + return nil, nil + } + + function := extern.Func() + if function == nil { + return nil, stdlib.WebAssemblyNonFunctionExportError{} + } + + return newWasmtimeFunctionWebAssemblyExport(gauge, function, i.Store) +} + +func newWasmtimeFunctionWebAssemblyExport( + gauge common.MemoryGauge, + function *wasmtime.Func, + store *wasmtime.Store, +) ( + *stdlib.WebAssemblyExport, + error, +) { + funcType := function.Type(store) + + functionType := &sema.FunctionType{} + + // Parameters + + for _, paramType := range funcType.Params() { + paramKind := paramType.Kind() + parameterType := wasmtimeValKindToSemaType(paramKind) + if parameterType == nil { + return nil, fmt.Errorf( + "unsupported export: function with unsupported parameter type '%s'", + paramKind, + ) + } + functionType.Parameters = append( + functionType.Parameters, + sema.Parameter{ + TypeAnnotation: sema.NewTypeAnnotation(parameterType), + }, + ) + } + + // Result + + results := funcType.Results() + switch len(results) { + case 0: + functionType.ReturnTypeAnnotation = sema.VoidTypeAnnotation + + case 1: + result := results[0] + resultKind := result.Kind() + returnType := wasmtimeValKindToSemaType(resultKind) + if returnType == nil { + return nil, fmt.Errorf( + "unsupported export: function with unsupported result type '%s'", + resultKind, + ) + } + functionType.ReturnTypeAnnotation = sema.NewTypeAnnotation(returnType) + + default: + return nil, fmt.Errorf("unsupported export: function has more than one result") + } + + metered := func(inter *interpreter.Interpreter, f func() (any, error)) (any, error) { + + fuelBefore := inter.ComputationRemaining(common.ComputationKindWebAssemblyFuel) + err := store.SetFuel(uint64(fuelBefore)) + if err != nil { + // "[SetFuel] will return an error if fuel consumption is not enabled" + panic(errors.NewUnreachableError()) + } + + callResult, callErr := f() + + // IMPORTANT: always report consumed fuel, even if there was an error + + fuelAfter, err := store.GetFuel() + if err != nil { + // "[GetFuel] will return an error if fuel consumption is not enabled" + panic(errors.NewUnreachableError()) + } + + fuelDelta := fuelBefore - uint(fuelAfter) + inter.ReportComputation(common.ComputationKindWebAssemblyFuel, fuelDelta) + + return callResult, callErr + } + + hostFunctionValue := interpreter.NewStaticHostFunctionValue( + gauge, + functionType, + func(invocation interpreter.Invocation) interpreter.Value { + arguments := invocation.Arguments + inter := invocation.Interpreter + + // Convert the arguments + + convertedArguments := make([]any, 0, len(arguments)) + + for i, argument := range arguments { + ty := functionType.Parameters[i].TypeAnnotation.Type + + var convertedArgument any + + switch ty { + case sema.Int32Type: + convertedArgument = int32(argument.(interpreter.Int32Value)) + + case sema.Int64Type: + convertedArgument = int64(argument.(interpreter.Int64Value)) + + default: + panic(errors.NewUnreachableError()) + } + + convertedArguments = append(convertedArguments, convertedArgument) + } + + // Call the function, with metering + + result, err := metered( + inter, + func() (any, error) { + res, err := function.Call(store, convertedArguments...) + if err != nil { + if _, ok := err.(*wasmtime.Trap); ok { + return nil, stdlib.WebAssemblyTrapError{} + } + + panic(errors.NewUnexpectedError("WebAssembly invocation failed with unknown error")) + } + return res, nil + }, + ) + if err != nil { + panic(err) + } + + // Return the result + + switch result := result.(type) { + case int32: + return interpreter.Int32Value(result) + + case int64: + return interpreter.Int64Value(result) + + default: + panic(errors.NewUnreachableError()) + + } + }, + ) + + return &stdlib.WebAssemblyExport{ + Type: functionType, + Value: hostFunctionValue, + }, nil +} diff --git a/runtime/webassembly_test.go b/runtime/webassembly_test.go new file mode 100644 index 0000000000..786d76f5ed --- /dev/null +++ b/runtime/webassembly_test.go @@ -0,0 +1,389 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package runtime_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/onflow/cadence" + "github.com/onflow/cadence/encoding/json" + . "github.com/onflow/cadence/runtime" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/stdlib" + . "github.com/onflow/cadence/runtime/tests/runtime_utils" + . "github.com/onflow/cadence/runtime/tests/utils" +) + +func TestRuntimeWebAssemblyAdd(t *testing.T) { + + t.Parallel() + + runtime := NewTestInterpreterRuntimeWithConfig(Config{ + WebAssemblyEnabled: true, + }) + + // A simple program which exports a function `add` with type `(i32, i32) -> i32`, + // which sums the arguments and returns the result: + // + // (module + // (type (;0;) (func (param i32 i32) (result i32))) + // (func (;0;) (type 0) (param i32 i32) (result i32) + // local.get 0 + // local.get 1 + // i32.add) + // (export "add" (func 0))) + // + addProgram := []byte{ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x60, + 0x02, 0x7f, 0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07, 0x07, 0x01, + 0x03, 0x61, 0x64, 0x64, 0x00, 0x00, 0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, + 0x00, 0x20, 0x01, 0x6a, 0x0b, + } + + // language=cadence + script := []byte(` + access(all) + fun main(program: [UInt8], a: Int32, b: Int32): Int32 { + let instance = WebAssembly.compileAndInstantiate(bytes: program).instance + let add = instance.getExport(name: "add") + return add(a, b) + add(a, b) + } + `) + + var webAssemblyFuelComputationMeterings []uint + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnCompileWebAssembly: NewWasmtimeWebAssemblyModule, + OnDecodeArgument: func(b []byte, _ cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + OnMeterComputation: func(compKind common.ComputationKind, intensity uint) error { + if compKind != common.ComputationKindWebAssemblyFuel { + return nil + } + + webAssemblyFuelComputationMeterings = append( + webAssemblyFuelComputationMeterings, + intensity, + ) + + return nil + }, + } + + result, err := runtime.ExecuteScript( + Script{ + Source: script, + Arguments: encodeArgs( + newBytesValue(addProgram), + cadence.Int32(1), + cadence.Int32(2), + ), + }, + Context{ + Interface: runtimeInterface, + Location: common.ScriptLocation{}, + }, + ) + + require.NoError(t, err) + assert.Equal(t, + cadence.Int32(6), + result, + ) + assert.Equal(t, + []uint{4, 4}, + webAssemblyFuelComputationMeterings, + ) +} + +func TestRuntimeWebAssemblyDisabled(t *testing.T) { + + t.Parallel() + + runtime := NewTestInterpreterRuntimeWithConfig(Config{ + WebAssemblyEnabled: false, + }) + + // language=cadence + script := []byte(` + access(all) + fun main() { + WebAssembly + } + `) + + runtimeInterface := &TestRuntimeInterface{} + + _, err := runtime.ExecuteScript( + Script{ + Source: script, + }, + Context{ + Interface: runtimeInterface, + Location: common.ScriptLocation{}, + }, + ) + RequireError(t, err) +} + +func TestRuntimeWebAssemblyLoop(t *testing.T) { + + t.Parallel() + + runtime := NewTestInterpreterRuntimeWithConfig(Config{ + WebAssemblyEnabled: true, + }) + + // A simple program which exports a function that loops forever: + // + // (module + // (func (loop $loop (br 0))) + // (export "loop" (func 0))) + // + addProgram := []byte{ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x04, 0x01, 0x60, + 0x00, 0x00, 0x03, 0x02, 0x01, 0x00, 0x07, 0x08, 0x01, 0x04, 0x6c, 0x6f, + 0x6f, 0x70, 0x00, 0x00, 0x0a, 0x09, 0x01, 0x07, 0x00, 0x03, 0x40, 0x0c, + 0x00, 0x0b, 0x0b, + } + + // language=cadence + script := []byte(` + access(all) + fun main(program: [UInt8]) { + let instance = WebAssembly.compileAndInstantiate(bytes: program).instance + let loop = instance.getExport(name: "loop") + loop() + } + `) + + var webAssemblyFuelComputationMeterings []uint + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnCompileWebAssembly: NewWasmtimeWebAssemblyModule, + OnDecodeArgument: func(b []byte, _ cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + OnMeterComputation: func(compKind common.ComputationKind, intensity uint) error { + if compKind != common.ComputationKindWebAssemblyFuel { + return nil + } + + webAssemblyFuelComputationMeterings = append( + webAssemblyFuelComputationMeterings, + intensity, + ) + + return nil + }, + OnComputationRemaining: func(kind common.ComputationKind) uint { + if kind != common.ComputationKindWebAssemblyFuel { + return 0 + } + + return 1000 + }, + } + + _, err := runtime.ExecuteScript( + Script{ + Source: script, + Arguments: encodeArgs( + newBytesValue(addProgram), + ), + }, + Context{ + Interface: runtimeInterface, + Location: common.ScriptLocation{}, + }, + ) + + require.Error(t, err) + require.ErrorAs(t, err, &stdlib.WebAssemblyTrapError{}) + + assert.Equal(t, + // TODO: adjust, currently todoAvailableFuel + []uint{1000}, + webAssemblyFuelComputationMeterings, + ) +} + +func TestRuntimeWebAssemblyInfiniteLoopAtStart(t *testing.T) { + + t.Parallel() + + runtime := NewTestInterpreterRuntimeWithConfig(Config{ + WebAssemblyEnabled: true, + }) + + // A simple program which loops forever, on start/load: + // + // (module + // (func (loop $loop (br 0))) + // (start 0)) + // + addProgram := []byte{ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x04, 0x01, 0x60, + 0x00, 0x00, 0x03, 0x02, 0x01, 0x00, 0x08, 0x01, 0x00, 0x0a, 0x09, 0x01, + 0x07, 0x00, 0x03, 0x40, 0x0c, 0x00, 0x0b, 0x0b, + } + + // language=cadence + script := []byte(` + access(all) + fun main(program: [UInt8]) { + let instance = WebAssembly.compileAndInstantiate(bytes: program).instance + } + `) + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnCompileWebAssembly: NewWasmtimeWebAssemblyModule, + OnDecodeArgument: func(b []byte, _ cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + OnComputationRemaining: func(kind common.ComputationKind) uint { + if kind != common.ComputationKindWebAssemblyFuel { + return 0 + } + + return 1000 + }, + } + + _, err := runtime.ExecuteScript( + Script{ + Source: script, + Arguments: encodeArgs( + newBytesValue(addProgram), + ), + }, + Context{ + Interface: runtimeInterface, + Location: common.ScriptLocation{}, + }, + ) + + RequireError(t, err) + require.ErrorAs(t, err, &stdlib.WebAssemblyTrapError{}) +} + +func TestRuntimeWebAssemblyNonFunctionExport(t *testing.T) { + + t.Parallel() + + runtime := NewTestInterpreterRuntimeWithConfig(Config{ + WebAssemblyEnabled: true, + }) + + // A simple program which exports a memory `add`. + // + // (module + // (memory (export "add") 1 4) + // (data (i32.const 0x1) "\01\02\03") + // ) + // + addProgram := []byte{ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x05, 0x04, 0x01, 0x01, + 0x01, 0x04, 0x07, 0x07, 0x01, 0x03, 0x61, 0x64, 0x64, 0x02, 0x00, 0x0b, + 0x09, 0x01, 0x00, 0x41, 0x01, 0x0b, 0x03, 0x01, 0x02, 0x03, + } + + // language=cadence + script := []byte(` + access(all) + fun main(program: [UInt8]) { + let instance = WebAssembly.compileAndInstantiate(bytes: program).instance + instance.getExport(name: "add") + } + `) + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnCompileWebAssembly: NewWasmtimeWebAssemblyModule, + OnDecodeArgument: func(b []byte, _ cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + } + + _, err := runtime.ExecuteScript( + Script{ + Source: script, + Arguments: encodeArgs( + newBytesValue(addProgram), + ), + }, + Context{ + Interface: runtimeInterface, + Location: common.ScriptLocation{}, + }, + ) + + RequireError(t, err) + require.ErrorAs(t, err, &stdlib.WebAssemblyNonFunctionExportError{}) +} + +func TestRuntimeWebAssemblyInvalidModule(t *testing.T) { + + t.Parallel() + + runtime := NewTestInterpreterRuntimeWithConfig(Config{ + WebAssemblyEnabled: true, + }) + + program := []byte{0xFF} + + // language=cadence + script := []byte(` + access(all) + fun main(program: [UInt8]) { + WebAssembly.compileAndInstantiate(bytes: program).instance + } + `) + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnCompileWebAssembly: NewWasmtimeWebAssemblyModule, + OnDecodeArgument: func(b []byte, _ cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + } + + _, err := runtime.ExecuteScript( + Script{ + Source: script, + Arguments: encodeArgs( + newBytesValue(program), + ), + }, + Context{ + Interface: runtimeInterface, + Location: common.ScriptLocation{}, + }, + ) + + RequireError(t, err) + require.ErrorAs(t, err, &stdlib.WebAssemblyCompilationError{}) +} diff --git a/tools/analysis/programs.go b/tools/analysis/programs.go index 08ae11ae49..6631cf2959 100644 --- a/tools/analysis/programs.go +++ b/tools/analysis/programs.go @@ -125,7 +125,10 @@ func (programs Programs) check( error, ) { baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) - for _, value := range stdlib.DefaultScriptStandardLibraryValues(nil) { + for _, value := range stdlib.DefaultScriptStandardLibraryValues( + nil, + stdlib.DefaultStandardLibraryOptions, + ) { baseValueActivation.DeclareValue(value) } diff --git a/tools/storage-explorer/go.mod b/tools/storage-explorer/go.mod index e29530edb8..58c07af0d9 100644 --- a/tools/storage-explorer/go.mod +++ b/tools/storage-explorer/go.mod @@ -23,6 +23,8 @@ require ( github.com/VictoriaMetrics/fastcache v1.12.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.2.1 // indirect + github.com/bytecodealliance/wasmtime-go/v22 v22.0.1 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash v1.1.0 // indirect diff --git a/tools/storage-explorer/go.sum b/tools/storage-explorer/go.sum index 54aea1efc8..03a03e3659 100644 --- a/tools/storage-explorer/go.sum +++ b/tools/storage-explorer/go.sum @@ -1009,6 +1009,8 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= github.com/aws/aws-sdk-go-v2 v1.21.2/go.mod h1:ErQhvNuEMhJjweavOYhxVkn2RUx7kQXVATHrjKtxIpM= github.com/aws/aws-sdk-go-v2 v1.23.1/go.mod h1:i1XDttT4rnf6vxc9AuskLc6s7XBee8rlLilKlc03uAA= +github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= +github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= github.com/aws/aws-sdk-go-v2 v1.27.0 h1:7bZWKoXhzI+mMR/HjdMx8ZCC5+6fY0lS5tr0bbgiLlo= github.com/aws/aws-sdk-go-v2 v1.27.0/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y= @@ -1034,6 +1036,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1: github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2/go.mod h1:45MfaXZ0cNbeuT0KQ1XJylq8A6+OpVV2E5kvY/Kq+u8= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37/go.mod h1:vBmDnwWXWxNPFRMmG2m/3MKOe+xEcMDo1tanpaWCcck= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4/go.mod h1:aYCGNjyUCUelhofxlZyj63srdxWUSsBSGg5l6MCuXuE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 h1:Wx0rlZoEJR7JwlSZcHnEa7CNjrSIyVxMFWGAaXy4fJY= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9/go.mod h1:aVMHdE0aHO3v+f/iw01fmXV/5DbfQ3Bi9nN7nd9bE9Y= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.7.0 h1:HWsM0YQWX76V6MOp07YuTYacm8k7h69ObJuw7Nck+og= @@ -1079,7 +1083,9 @@ github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/bytecodealliance/wasmtime-go/v7 v7.0.0/go.mod h1:bu6fic7trDt20w+LMooX7j3fsOwv4/ln6j8gAdP6vmA= +github.com/bytecodealliance/wasmtime-go/v22 v22.0.0-20240626142402-d03b414c50b4 h1:Jtz9TgzQLwJgPZfF7gEcM8D0Vk6CizoV6UUdLmS/SiU= +github.com/bytecodealliance/wasmtime-go/v22 v22.0.0-20240626142402-d03b414c50b4/go.mod h1:knqkvjTLavLtAXnA5NJUM0qbRiPJCVWPLXfYu75kZSo= +github.com/bytecodealliance/wasmtime-go/v22 v22.0.1/go.mod h1:knqkvjTLavLtAXnA5NJUM0qbRiPJCVWPLXfYu75kZSo= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= diff --git a/vm/vm.go b/vm/vm.go index 510a5dcfb9..fd981f8282 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -27,7 +27,7 @@ import ( "C" - "github.com/bytecodealliance/wasmtime-go/v7" + "github.com/bytecodealliance/wasmtime-go/v25" "github.com/onflow/cadence/runtime/interpreter" )