From d4b8e93d22fba5ee9631af7319e5a4b4d837090e Mon Sep 17 00:00:00 2001 From: Janez Podhostnik Date: Wed, 23 Aug 2023 17:27:46 +0200 Subject: [PATCH 1/3] Make code and programs public --- runtime/context.go | 12 ++++++------ runtime/contract_function_executor.go | 4 ++-- runtime/environment.go | 6 +++--- runtime/errors.go | 2 +- runtime/runtime.go | 12 ++++++------ runtime/script_executor.go | 4 ++-- runtime/transaction_executor.go | 4 ++-- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/runtime/context.go b/runtime/context.go index df87da6d8d..c1344856ba 100644 --- a/runtime/context.go +++ b/runtime/context.go @@ -29,24 +29,24 @@ type Context struct { CoverageReport *CoverageReport } -// codesAndPrograms collects the source code and AST for each location. +// CodesAndPrograms collects the source code and AST for each location. // It is purely used for debugging: Both the codes and the programs // are provided in runtime errors. -type codesAndPrograms struct { +type CodesAndPrograms struct { codes map[Location][]byte programs map[Location]*ast.Program } -func (c codesAndPrograms) setCode(location Location, code []byte) { +func (c CodesAndPrograms) setCode(location Location, code []byte) { c.codes[location] = code } -func (c codesAndPrograms) setProgram(location Location, program *ast.Program) { +func (c CodesAndPrograms) setProgram(location Location, program *ast.Program) { c.programs[location] = program } -func newCodesAndPrograms() codesAndPrograms { - return codesAndPrograms{ +func NewCodesAndPrograms() CodesAndPrograms { + return CodesAndPrograms{ codes: map[Location][]byte{}, programs: map[Location]*ast.Program{}, } diff --git a/runtime/contract_function_executor.go b/runtime/contract_function_executor.go index db7e39115a..92f091de0f 100644 --- a/runtime/contract_function_executor.go +++ b/runtime/contract_function_executor.go @@ -34,7 +34,7 @@ type interpreterContractFunctionExecutor struct { result cadence.Value executeErr error preprocessErr error - codesAndPrograms codesAndPrograms + codesAndPrograms CodesAndPrograms runtime *interpreterRuntime storage *Storage contractLocation common.AddressLocation @@ -90,7 +90,7 @@ func (executor *interpreterContractFunctionExecutor) preprocess() (err error) { context := executor.context location := context.Location - codesAndPrograms := newCodesAndPrograms() + codesAndPrograms := NewCodesAndPrograms() executor.codesAndPrograms = codesAndPrograms interpreterRuntime := executor.runtime diff --git a/runtime/environment.go b/runtime/environment.go index 153d2ed217..32a5f8b024 100644 --- a/runtime/environment.go +++ b/runtime/environment.go @@ -41,7 +41,7 @@ type Environment interface { Declare(valueDeclaration stdlib.StandardLibraryValue) Configure( runtimeInterface Interface, - codesAndPrograms codesAndPrograms, + codesAndPrograms CodesAndPrograms, storage *Storage, coverageReport *CoverageReport, ) @@ -73,7 +73,7 @@ type interpreterEnvironmentReconfigured struct { runtimeInterface Interface storage *Storage coverageReport *CoverageReport - codesAndPrograms codesAndPrograms + codesAndPrograms CodesAndPrograms } type interpreterEnvironment struct { @@ -186,7 +186,7 @@ func NewScriptInterpreterEnvironment(config Config) Environment { func (e *interpreterEnvironment) Configure( runtimeInterface Interface, - codesAndPrograms codesAndPrograms, + codesAndPrograms CodesAndPrograms, storage *Storage, coverageReport *CoverageReport, ) { diff --git a/runtime/errors.go b/runtime/errors.go index 5c899c36c7..183642b259 100644 --- a/runtime/errors.go +++ b/runtime/errors.go @@ -37,7 +37,7 @@ type Error struct { Programs map[Location]*ast.Program } -func newError(err error, location Location, codesAndPrograms codesAndPrograms) Error { +func newError(err error, location Location, codesAndPrograms CodesAndPrograms) Error { return Error{ Err: err, Location: location, diff --git a/runtime/runtime.go b/runtime/runtime.go index d34514f59f..b9b33c3a7c 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -224,7 +224,7 @@ func (r *interpreterRuntime) Config() Config { return r.defaultConfig } -func (r *interpreterRuntime) Recover(onError func(Error), location Location, codesAndPrograms codesAndPrograms) { +func (r *interpreterRuntime) Recover(onError func(Error), location Location, codesAndPrograms CodesAndPrograms) { recovered := recover() if recovered == nil { return @@ -234,7 +234,7 @@ func (r *interpreterRuntime) Recover(onError func(Error), location Location, cod onError(err) } -func getWrappedError(recovered any, location Location, codesAndPrograms codesAndPrograms) Error { +func getWrappedError(recovered any, location Location, codesAndPrograms CodesAndPrograms) Error { switch recovered := recovered.(type) { // If the error is already a `runtime.Error`, then avoid redundant wrapping. @@ -513,7 +513,7 @@ func (r *interpreterRuntime) ParseAndCheckProgram( ) { location := context.Location - codesAndPrograms := newCodesAndPrograms() + codesAndPrograms := NewCodesAndPrograms() defer r.Recover( func(internalErr Error) { @@ -552,7 +552,7 @@ func (r *interpreterRuntime) Storage(context Context) (*Storage, *interpreter.In location := context.Location - codesAndPrograms := newCodesAndPrograms() + codesAndPrograms := NewCodesAndPrograms() storage := NewStorage(context.Interface, context.Interface) @@ -589,7 +589,7 @@ func (r *interpreterRuntime) ReadStored( ) { location := context.Location - var codesAndPrograms codesAndPrograms + var codesAndPrograms CodesAndPrograms defer r.Recover( func(internalErr Error) { @@ -635,7 +635,7 @@ func (r *interpreterRuntime) ReadLinked( ) { location := context.Location - var codesAndPrograms codesAndPrograms + var codesAndPrograms CodesAndPrograms defer r.Recover( func(internalErr Error) { diff --git a/runtime/script_executor.go b/runtime/script_executor.go index 8ce26c534c..fffe5eb9e9 100644 --- a/runtime/script_executor.go +++ b/runtime/script_executor.go @@ -29,7 +29,7 @@ import ( type interpreterScriptExecutorPreparation struct { environment Environment preprocessErr error - codesAndPrograms codesAndPrograms + codesAndPrograms CodesAndPrograms functionEntryPointType *sema.FunctionType program *interpreter.Program storage *Storage @@ -92,7 +92,7 @@ func (executor *interpreterScriptExecutor) preprocess() (err error) { location := context.Location script := executor.script - codesAndPrograms := newCodesAndPrograms() + codesAndPrograms := NewCodesAndPrograms() executor.codesAndPrograms = codesAndPrograms interpreterRuntime := executor.runtime diff --git a/runtime/transaction_executor.go b/runtime/transaction_executor.go index f7f6ccc192..aa8a006c17 100644 --- a/runtime/transaction_executor.go +++ b/runtime/transaction_executor.go @@ -28,7 +28,7 @@ import ( ) type interpreterTransactionExecutorPreparation struct { - codesAndPrograms codesAndPrograms + codesAndPrograms CodesAndPrograms environment Environment preprocessErr error transactionType *sema.TransactionType @@ -92,7 +92,7 @@ func (executor *interpreterTransactionExecutor) preprocess() (err error) { location := context.Location script := executor.script - codesAndPrograms := newCodesAndPrograms() + codesAndPrograms := NewCodesAndPrograms() executor.codesAndPrograms = codesAndPrograms interpreterRuntime := executor.runtime From 3142b4f2c95f19ef68edabf30541824c6d817b08 Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Mon, 4 Sep 2023 14:17:27 +0300 Subject: [PATCH 2/3] Add function for testing framework's blockchain to create/load snapshots --- runtime/stdlib/contracts/test.cdc | 32 +++++ runtime/stdlib/test-framework.go | 4 + runtime/stdlib/test_emulatorbackend.go | 86 ++++++++++++ runtime/stdlib/test_test.go | 175 +++++++++++++++++++++++++ 4 files changed, 297 insertions(+) diff --git a/runtime/stdlib/contracts/test.cdc b/runtime/stdlib/contracts/test.cdc index 10dc61151d..ad858a8262 100644 --- a/runtime/stdlib/contracts/test.cdc +++ b/runtime/stdlib/contracts/test.cdc @@ -134,6 +134,27 @@ pub contract Test { pub fun moveTime(by delta: Fix64) { self.backend.moveTime(by: delta) } + + /// Creates a snapshot of the blockchain, at the + /// current ledger state, with the given name. + /// + pub fun createSnapshot(_ name: String) { + let err = self.backend.createSnapshot(name) + if err != nil { + panic(err!.message) + } + } + + /// Loads a snapshot of the blockchain, with the + /// given name, and updates the current ledger + /// state. + /// + pub fun loadSnapshot(_ name: String) { + let err = self.backend.loadSnapshot(name) + if err != nil { + panic(err!.message) + } + } } pub struct Matcher { @@ -324,6 +345,17 @@ pub contract Test { /// which should be passed in the form of seconds. /// pub fun moveTime(by delta: Fix64) + + /// Creates a snapshot of the blockchain, at the + /// current ledger state, with the given name. + /// + pub fun createSnapshot(_ name: String): Error? + + /// Loads a snapshot of the blockchain, with the + /// given name, and updates the current ledger + /// state. + /// + pub fun loadSnapshot(_ name: String): Error? } /// Returns a new matcher that negates the test of the given matcher. diff --git a/runtime/stdlib/test-framework.go b/runtime/stdlib/test-framework.go index e5f352747c..3e8d0523c2 100644 --- a/runtime/stdlib/test-framework.go +++ b/runtime/stdlib/test-framework.go @@ -79,6 +79,10 @@ type Blockchain interface { Reset(uint64) MoveTime(int64) + + CreateSnapshot(string) error + + LoadSnapshot(string) error } type ScriptResult struct { diff --git a/runtime/stdlib/test_emulatorbackend.go b/runtime/stdlib/test_emulatorbackend.go index 757e19af7b..aca8706e56 100644 --- a/runtime/stdlib/test_emulatorbackend.go +++ b/runtime/stdlib/test_emulatorbackend.go @@ -47,6 +47,8 @@ type testEmulatorBackendType struct { eventsFunctionType *sema.FunctionType resetFunctionType *sema.FunctionType moveTimeFunctionType *sema.FunctionType + createSnapshotFunctionType *sema.FunctionType + loadSnapshotFunctionType *sema.FunctionType } func newTestEmulatorBackendType( @@ -112,6 +114,16 @@ func newTestEmulatorBackendType( testEmulatorBackendTypeMoveTimeFunctionName, ) + createSnapshotFunctionType := interfaceFunctionType( + blockchainBackendInterfaceType, + testEmulatorBackendTypeCreateSnapshotFunctionName, + ) + + loadSnapshotFunctionType := interfaceFunctionType( + blockchainBackendInterfaceType, + testEmulatorBackendTypeLoadSnapshotFunctionName, + ) + compositeType := &sema.CompositeType{ Identifier: testEmulatorBackendTypeName, Kind: common.CompositeKindStructure, @@ -194,6 +206,18 @@ func newTestEmulatorBackendType( moveTimeFunctionType, testEmulatorBackendTypeMoveTimeFunctionDocString, ), + sema.NewUnmeteredPublicFunctionMember( + compositeType, + testEmulatorBackendTypeCreateSnapshotFunctionName, + createSnapshotFunctionType, + testEmulatorBackendTypeCreateSnapshotFunctionDocString, + ), + sema.NewUnmeteredPublicFunctionMember( + compositeType, + testEmulatorBackendTypeLoadSnapshotFunctionName, + loadSnapshotFunctionType, + testEmulatorBackendTypeLoadSnapshotFunctionDocString, + ), } compositeType.Members = sema.MembersAsMap(members) @@ -213,6 +237,8 @@ func newTestEmulatorBackendType( eventsFunctionType: eventsFunctionType, resetFunctionType: resetFunctionType, moveTimeFunctionType: moveTimeFunctionType, + createSnapshotFunctionType: createSnapshotFunctionType, + loadSnapshotFunctionType: loadSnapshotFunctionType, } } @@ -739,6 +765,58 @@ func (t *testEmulatorBackendType) newMoveTimeFunction( ) } +// 'Emulator.createSnapshot' function + +const testEmulatorBackendTypeCreateSnapshotFunctionName = "createSnapshot" + +const testEmulatorBackendTypeCreateSnapshotFunctionDocString = ` +Creates a snapshot of the blockchain, at the +current ledger state, with the given name. +` + +func (t *testEmulatorBackendType) newCreateSnapshotFunction( + blockchain Blockchain, +) *interpreter.HostFunctionValue { + return interpreter.NewUnmeteredHostFunctionValue( + t.createSnapshotFunctionType, + func(invocation interpreter.Invocation) interpreter.Value { + name, ok := invocation.Arguments[0].(*interpreter.StringValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + err := blockchain.CreateSnapshot(name.Str) + return newErrorValue(invocation.Interpreter, err) + }, + ) +} + +// 'Emulator.loadSnapshot' function + +const testEmulatorBackendTypeLoadSnapshotFunctionName = "loadSnapshot" + +const testEmulatorBackendTypeLoadSnapshotFunctionDocString = ` +Loads a snapshot of the blockchain, with the given name, and +updates the current ledger state. +` + +func (t *testEmulatorBackendType) newLoadSnapshotFunction( + blockchain Blockchain, +) *interpreter.HostFunctionValue { + return interpreter.NewUnmeteredHostFunctionValue( + t.loadSnapshotFunctionType, + func(invocation interpreter.Invocation) interpreter.Value { + name, ok := invocation.Arguments[0].(*interpreter.StringValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + err := blockchain.LoadSnapshot(name.Str) + return newErrorValue(invocation.Interpreter, err) + }, + ) +} + func (t *testEmulatorBackendType) newEmulatorBackend( inter *interpreter.Interpreter, blockchain Blockchain, @@ -792,6 +870,14 @@ func (t *testEmulatorBackendType) newEmulatorBackend( Name: testEmulatorBackendTypeMoveTimeFunctionName, Value: t.newMoveTimeFunction(blockchain), }, + { + Name: testEmulatorBackendTypeCreateSnapshotFunctionName, + Value: t.newCreateSnapshotFunction(blockchain), + }, + { + Name: testEmulatorBackendTypeLoadSnapshotFunctionName, + Value: t.newLoadSnapshotFunction(blockchain), + }, } // TODO: Use SimpleCompositeValue diff --git a/runtime/stdlib/test_test.go b/runtime/stdlib/test_test.go index ff0291a384..40c551e524 100644 --- a/runtime/stdlib/test_test.go +++ b/runtime/stdlib/test_test.go @@ -20,6 +20,7 @@ package stdlib import ( "errors" + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -2339,6 +2340,162 @@ func TestBlockchain(t *testing.T) { assert.True(t, newEmulatorBackendInvoked) }) + t.Run("createSnapshot", func(t *testing.T) { + t.Parallel() + + const script = ` + import Test + + pub fun test() { + let blockchain = Test.newEmulatorBlockchain() + blockchain.createSnapshot("adminCreated") + } + ` + + createSnapshotInvoked := false + + testFramework := &mockedTestFramework{ + newEmulatorBackend: func() Blockchain { + return &mockedBlockchain{ + createSnapshot: func(name string) error { + createSnapshotInvoked = true + assert.Equal(t, "adminCreated", name) + + return nil + }, + } + }, + } + + inter, err := newTestContractInterpreterWithTestFramework(t, script, testFramework) + require.NoError(t, err) + + _, err = inter.Invoke("test") + require.NoError(t, err) + + assert.True(t, createSnapshotInvoked) + }) + + t.Run("createSnapshot failure", func(t *testing.T) { + t.Parallel() + + const script = ` + import Test + + pub fun test() { + let blockchain = Test.newEmulatorBlockchain() + blockchain.createSnapshot("adminCreated") + } + ` + + createSnapshotInvoked := false + + testFramework := &mockedTestFramework{ + newEmulatorBackend: func() Blockchain { + return &mockedBlockchain{ + createSnapshot: func(name string) error { + createSnapshotInvoked = true + assert.Equal(t, "adminCreated", name) + + return fmt.Errorf("failed to create snapshot: %s", name) + }, + } + }, + } + + inter, err := newTestContractInterpreterWithTestFramework(t, script, testFramework) + require.NoError(t, err) + + _, err = inter.Invoke("test") + require.ErrorContains(t, err, "panic: failed to create snapshot: adminCreated") + + assert.True(t, createSnapshotInvoked) + }) + + t.Run("loadSnapshot", func(t *testing.T) { + t.Parallel() + + const script = ` + import Test + + pub fun test() { + let blockchain = Test.newEmulatorBlockchain() + blockchain.createSnapshot("adminCreated") + blockchain.loadSnapshot("adminCreated") + } + ` + + loadSnapshotInvoked := false + + testFramework := &mockedTestFramework{ + newEmulatorBackend: func() Blockchain { + return &mockedBlockchain{ + createSnapshot: func(name string) error { + assert.Equal(t, "adminCreated", name) + + return nil + }, + loadSnapshot: func(name string) error { + loadSnapshotInvoked = true + assert.Equal(t, "adminCreated", name) + + return nil + }, + } + }, + } + + inter, err := newTestContractInterpreterWithTestFramework(t, script, testFramework) + require.NoError(t, err) + + _, err = inter.Invoke("test") + require.NoError(t, err) + + assert.True(t, loadSnapshotInvoked) + }) + + t.Run("loadSnapshot failure", func(t *testing.T) { + t.Parallel() + + const script = ` + import Test + + pub fun test() { + let blockchain = Test.newEmulatorBlockchain() + blockchain.createSnapshot("adminCreated") + blockchain.loadSnapshot("contractDeployed") + } + ` + + loadSnapshotInvoked := false + + testFramework := &mockedTestFramework{ + newEmulatorBackend: func() Blockchain { + return &mockedBlockchain{ + createSnapshot: func(name string) error { + assert.Equal(t, "adminCreated", name) + + return nil + }, + loadSnapshot: func(name string) error { + loadSnapshotInvoked = true + assert.Equal(t, "contractDeployed", name) + + return fmt.Errorf("failed to create snapshot: %s", name) + }, + } + }, + } + + inter, err := newTestContractInterpreterWithTestFramework(t, script, testFramework) + require.NoError(t, err) + + _, err = inter.Invoke("test") + require.ErrorContains(t, err, "panic: failed to create snapshot: contractDeployed") + + assert.True(t, loadSnapshotInvoked) + }) + // TODO: Add more tests for the remaining functions. } @@ -2379,6 +2536,8 @@ type mockedBlockchain struct { events func(inter *interpreter.Interpreter, eventType interpreter.StaticType) interpreter.Value reset func(uint64) moveTime func(int64) + createSnapshot func(string) error + loadSnapshot func(string) error } var _ Blockchain = &mockedBlockchain{} @@ -2505,3 +2664,19 @@ func (m mockedBlockchain) MoveTime(timeDelta int64) { m.moveTime(timeDelta) } + +func (m mockedBlockchain) CreateSnapshot(name string) error { + if m.createSnapshot == nil { + panic("'CreateSnapshot' is not implemented") + } + + return m.createSnapshot(name) +} + +func (m mockedBlockchain) LoadSnapshot(name string) error { + if m.loadSnapshot == nil { + panic("'LoadSnapshot' is not implemented") + } + + return m.loadSnapshot(name) +} From 0c10225154a7124956a534e6cc484850989c615a Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Wed, 6 Sep 2023 10:12:41 +0300 Subject: [PATCH 3/3] Update access modifier and make argument label required for snapshot methods --- runtime/stdlib/contracts/test.cdc | 16 ++++++++++------ runtime/stdlib/test_test.go | 12 ++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/runtime/stdlib/contracts/test.cdc b/runtime/stdlib/contracts/test.cdc index ad858a8262..b6041f7b9e 100644 --- a/runtime/stdlib/contracts/test.cdc +++ b/runtime/stdlib/contracts/test.cdc @@ -138,8 +138,9 @@ pub contract Test { /// Creates a snapshot of the blockchain, at the /// current ledger state, with the given name. /// - pub fun createSnapshot(_ name: String) { - let err = self.backend.createSnapshot(name) + access(all) + fun createSnapshot(name: String) { + let err = self.backend.createSnapshot(name: name) if err != nil { panic(err!.message) } @@ -149,8 +150,9 @@ pub contract Test { /// given name, and updates the current ledger /// state. /// - pub fun loadSnapshot(_ name: String) { - let err = self.backend.loadSnapshot(name) + access(all) + fun loadSnapshot(name: String) { + let err = self.backend.loadSnapshot(name: name) if err != nil { panic(err!.message) } @@ -349,13 +351,15 @@ pub contract Test { /// Creates a snapshot of the blockchain, at the /// current ledger state, with the given name. /// - pub fun createSnapshot(_ name: String): Error? + access(all) + fun createSnapshot(name: String): Error? /// Loads a snapshot of the blockchain, with the /// given name, and updates the current ledger /// state. /// - pub fun loadSnapshot(_ name: String): Error? + access(all) + fun loadSnapshot(name: String): Error? } /// Returns a new matcher that negates the test of the given matcher. diff --git a/runtime/stdlib/test_test.go b/runtime/stdlib/test_test.go index 40c551e524..beaa3a2f68 100644 --- a/runtime/stdlib/test_test.go +++ b/runtime/stdlib/test_test.go @@ -2348,7 +2348,7 @@ func TestBlockchain(t *testing.T) { pub fun test() { let blockchain = Test.newEmulatorBlockchain() - blockchain.createSnapshot("adminCreated") + blockchain.createSnapshot(name: "adminCreated") } ` @@ -2384,7 +2384,7 @@ func TestBlockchain(t *testing.T) { pub fun test() { let blockchain = Test.newEmulatorBlockchain() - blockchain.createSnapshot("adminCreated") + blockchain.createSnapshot(name: "adminCreated") } ` @@ -2420,8 +2420,8 @@ func TestBlockchain(t *testing.T) { pub fun test() { let blockchain = Test.newEmulatorBlockchain() - blockchain.createSnapshot("adminCreated") - blockchain.loadSnapshot("adminCreated") + blockchain.createSnapshot(name: "adminCreated") + blockchain.loadSnapshot(name: "adminCreated") } ` @@ -2462,8 +2462,8 @@ func TestBlockchain(t *testing.T) { pub fun test() { let blockchain = Test.newEmulatorBlockchain() - blockchain.createSnapshot("adminCreated") - blockchain.loadSnapshot("contractDeployed") + blockchain.createSnapshot(name: "adminCreated") + blockchain.loadSnapshot(name: "contractDeployed") } `