diff --git a/engine/execution/state/bootstrap/bootstrap_test.go b/engine/execution/state/bootstrap/bootstrap_test.go index aee578b7729..ae239b59378 100644 --- a/engine/execution/state/bootstrap/bootstrap_test.go +++ b/engine/execution/state/bootstrap/bootstrap_test.go @@ -58,7 +58,7 @@ func TestBootstrapLedger(t *testing.T) { func TestBootstrapLedger_ZeroTokenSupply(t *testing.T) { expectedStateCommitmentBytes, _ := hex.DecodeString( - "072ff5128df34db4483879f1b617338cd51def0e55cae25927eb5f3b404f3ef9", + "506f0c7c9c12fb9b37deb015e3f148dd11ed139030fa0a6eeb6b162c73e4fb14", ) expectedStateCommitment, err := flow.ToStateCommitment(expectedStateCommitmentBytes) require.NoError(t, err) @@ -107,7 +107,7 @@ func TestBootstrapLedger_ZeroTokenSupply(t *testing.T) { // This tests that the state commitment has not changed for the bookkeeping parts of the transaction. func TestBootstrapLedger_EmptyTransaction(t *testing.T) { expectedStateCommitmentBytes, _ := hex.DecodeString( - "61acd22132b90efd23bb1c4413ae5071dc9c0e123c288371a755e4f041791a20", + "a16a6bf392c9dcffda1baeea9e70f1f20c504aeb912a1ed5cfadca65feb962ce", ) expectedStateCommitment, err := flow.ToStateCommitment(expectedStateCommitmentBytes) require.NoError(t, err) diff --git a/fvm/bootstrap.go b/fvm/bootstrap.go index 9fb684a0c88..6cd8943180a 100644 --- a/fvm/bootstrap.go +++ b/fvm/bootstrap.go @@ -249,6 +249,7 @@ func Bootstrap( ServiceAccountPublicKeys: []flow.AccountPublicKey{serviceAccountPublicKey}, FungibleTokenAccountPublicKeys: []flow.AccountPublicKey{serviceAccountPublicKey}, FlowTokenAccountPublicKeys: []flow.AccountPublicKey{serviceAccountPublicKey}, + FlowFeesAccountPublicKeys: []flow.AccountPublicKey{serviceAccountPublicKey}, NodeAccountPublicKeys: []flow.AccountPublicKey{serviceAccountPublicKey}, }, transactionFees: BootstrapProcedureFeeParameters{0, 0, 0}, diff --git a/fvm/fvm_test.go b/fvm/fvm_test.go index 23899df03b8..49c0cfe74ff 100644 --- a/fvm/fvm_test.go +++ b/fvm/fvm_test.go @@ -7,6 +7,7 @@ import ( "encoding/hex" "fmt" "math" + "strconv" "strings" "testing" @@ -4277,3 +4278,82 @@ func Test_BlockHashListShouldWriteOnPush(t *testing.T) { require.Equal(t, expectedBlockHashListBucket, newBlockHashListBucket) })) } + +func TestTransactionIndexCall(t *testing.T) { + t.Parallel() + + t.Run("in transactions", + newVMTest(). + run( + func( + t *testing.T, + vm fvm.VM, + chain flow.Chain, + ctx fvm.Context, + snapshotTree snapshot.SnapshotTree, + ) { + txBodyBuilder := flow.NewTransactionBodyBuilder(). + SetScript([]byte(` + transaction { + prepare() { + let idx = getTransactionIndex() + log(idx) + } + } + `)). + SetProposalKey(chain.ServiceAddress(), 0, 0). + SetPayer(chain.ServiceAddress()) + + err := testutil.SignTransactionAsServiceAccount(txBodyBuilder, 0, chain) + require.NoError(t, err) + + txBody, err := txBodyBuilder.Build() + require.NoError(t, err) + + ctx = fvm.NewContextFromParent(ctx, fvm.WithCadenceLogging(true)) + + txIndex := uint32(3) + + _, output, err := vm.Run( + ctx, + fvm.Transaction(txBody, txIndex), + snapshotTree) + require.NoError(t, err) + require.NoError(t, output.Err) + require.Len(t, output.Logs, 1) + + idx, err := strconv.Atoi(output.Logs[0]) + require.NoError(t, err) + require.Equal(t, txIndex, uint32(idx)) + }, + ), + ) + + t.Run("in scripts", + newVMTest(). + run( + func( + t *testing.T, + vm fvm.VM, + chain flow.Chain, + ctx fvm.Context, + snapshotTree snapshot.SnapshotTree, + ) { + script := fvm.Script( + []byte(` + access(all) fun main(): UInt32 { + return getTransactionIndex() + }`)) + + _, output, err := vm.Run( + ctx, + script, + snapshotTree) + require.NoError(t, err) + require.NoError(t, output.Err) + + require.Equal(t, cadence.UInt32(0), output.Value) + }, + ), + ) +} diff --git a/fvm/runtime/cadence_function_declarations.go b/fvm/runtime/cadence_function_declarations.go new file mode 100644 index 00000000000..00b077cb9e7 --- /dev/null +++ b/fvm/runtime/cadence_function_declarations.go @@ -0,0 +1,82 @@ +package runtime + +import ( + "github.com/onflow/cadence/common" + "github.com/onflow/cadence/interpreter" + "github.com/onflow/cadence/sema" + "github.com/onflow/cadence/stdlib" + + "github.com/onflow/flow-go/fvm/errors" +) + +// randomSourceFunctionType is the type of the `randomSource` function. +// This defines the signature as `func(): [UInt8]` +var randomSourceFunctionType = &sema.FunctionType{ + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.ByteArrayType), +} + +func blockRandomSourceDeclaration(renv *ReusableCadenceRuntime) stdlib.StandardLibraryValue { + // Declare the `randomSourceHistory` function. This function is **only** used by the + // System transaction, to fill the `RandomBeaconHistory` contract via the heartbeat + // resource. This allows the `RandomBeaconHistory` contract to be a standard contract, + // without any special parts. + // Since the `randomSourceHistory` function is only used by the System transaction, + // it is not part of the cadence standard library, and can just be injected from here. + // It also doesn't need user documentation, since it is not (and should not) + // be called by the user. If it is called by the user it will panic. + return stdlib.StandardLibraryValue{ + Name: "randomSourceHistory", + Type: randomSourceFunctionType, + Kind: common.DeclarationKindFunction, + Value: interpreter.NewUnmeteredStaticHostFunctionValue( + randomSourceFunctionType, + func(invocation interpreter.Invocation) interpreter.Value { + var err error + var source []byte + env := renv.fvmEnv + if env != nil { + source, err = env.RandomSourceHistory() + } else { + err = errors.NewOperationNotSupportedError("randomSourceHistory") + } + + if err != nil { + panic(err) + } + + return interpreter.ByteSliceToByteArrayValue( + invocation.InvocationContext, + source) + }, + ), + } +} + +// transactionIndexFunctionType is the type of the `getTransactionIndex` function. +// This defines the signature as `func(): UInt32` +var transactionIndexFunctionType = &sema.FunctionType{ + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.UInt32Type), +} + +func transactionIndexDeclaration(renv *ReusableCadenceRuntime) stdlib.StandardLibraryValue { + return stdlib.StandardLibraryValue{ + Name: "getTransactionIndex", + DocString: `Returns the transaction index in the current block, i.e. first transaction in a block has index of 0, second has index of 1...`, + Type: transactionIndexFunctionType, + Kind: common.DeclarationKindFunction, + Value: interpreter.NewUnmeteredStaticHostFunctionValue( + transactionIndexFunctionType, + func(invocation interpreter.Invocation) interpreter.Value { + return interpreter.NewUInt32Value( + invocation.InvocationContext, + func() uint32 { + env := renv.fvmEnv + if env == nil { + panic(errors.NewOperationNotSupportedError("transactionIndex")) + } + return env.TxIndex() + }) + }, + ), + } +} diff --git a/fvm/runtime/reusable_cadence_runtime.go b/fvm/runtime/reusable_cadence_runtime.go index 7c6c3c318f0..501112656a1 100644 --- a/fvm/runtime/reusable_cadence_runtime.go +++ b/fvm/runtime/reusable_cadence_runtime.go @@ -3,12 +3,8 @@ package runtime import ( "github.com/onflow/cadence" "github.com/onflow/cadence/common" - "github.com/onflow/cadence/interpreter" "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/sema" - "github.com/onflow/cadence/stdlib" - - "github.com/onflow/flow-go/fvm/errors" ) // Note: this is a subset of environment.Environment, redeclared to handle @@ -18,12 +14,7 @@ type Environment interface { common.Gauge RandomSourceHistory() ([]byte, error) -} - -// randomSourceFunctionType is the type of the `randomSource` function. -// This defines the signature as `func(): [UInt8]` -var randomSourceFunctionType = &sema.FunctionType{ - ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.ByteArrayType), + TxIndex() uint32 } type ReusableCadenceRuntime struct { @@ -44,63 +35,17 @@ func NewReusableCadenceRuntime( ScriptRuntimeEnv: runtime.NewScriptInterpreterEnvironment(config), } - reusable.declareRandomSourceHistory() + reusable.declareStandardLibraryFunctions() return reusable } -func (reusable *ReusableCadenceRuntime) declareRandomSourceHistory() { - - // Declare the `randomSourceHistory` function. This function is **only** used by the - // System transaction, to fill the `RandomBeaconHistory` contract via the heartbeat - // resource. This allows the `RandomBeaconHistory` contract to be a standard contract, - // without any special parts. - // Since the `randomSourceHistory` function is only used by the System transaction, - // it is not part of the cadence standard library, and can just be injected from here. - // It also doesnt need user documentation, since it is not (and should not) - // be called by the user. If it is called by the user it will panic. - functionType := randomSourceFunctionType - - blockRandomSource := stdlib.StandardLibraryValue{ - Name: "randomSourceHistory", - Type: functionType, - Kind: common.DeclarationKindFunction, - Value: interpreter.NewUnmeteredStaticHostFunctionValue( - functionType, - func(invocation interpreter.Invocation) interpreter.Value { - - actualArgumentCount := len(invocation.Arguments) - expectedArgumentCount := len(functionType.Parameters) - - if actualArgumentCount != expectedArgumentCount { - panic(errors.NewInvalidArgumentErrorf( - "incorrect number of arguments: got %d, expected %d", - actualArgumentCount, - expectedArgumentCount, - )) - } - - var err error - var source []byte - fvmEnv := reusable.fvmEnv - if fvmEnv != nil { - source, err = fvmEnv.RandomSourceHistory() - } else { - err = errors.NewOperationNotSupportedError("randomSourceHistory") - } - - if err != nil { - panic(err) - } - - return interpreter.ByteSliceToByteArrayValue( - invocation.InvocationContext, - source) - }, - ), - } +func (reusable *ReusableCadenceRuntime) declareStandardLibraryFunctions() { + reusable.TxRuntimeEnv.DeclareValue(blockRandomSourceDeclaration(reusable), nil) - reusable.TxRuntimeEnv.DeclareValue(blockRandomSource, nil) + declaration := transactionIndexDeclaration(reusable) + reusable.TxRuntimeEnv.DeclareValue(declaration, nil) + reusable.ScriptRuntimeEnv.DeclareValue(declaration, nil) } func (reusable *ReusableCadenceRuntime) SetFvmEnvironment(fvmEnv Environment) { diff --git a/utils/unittest/execution_state.go b/utils/unittest/execution_state.go index 53e082cf163..a99b75dbf8f 100644 --- a/utils/unittest/execution_state.go +++ b/utils/unittest/execution_state.go @@ -23,7 +23,7 @@ const ServiceAccountPrivateKeySignAlgo = crypto.ECDSAP256 const ServiceAccountPrivateKeyHashAlgo = hash.SHA2_256 // Pre-calculated state commitment with root account with the above private key -const GenesisStateCommitmentHex = "dad6fda8f9745ec0e1cf9d4d2429060a9460cef513f29272a6deb973820af6aa" +const GenesisStateCommitmentHex = "e53b39d66b2689882f21d0a0605de5693df8090c01279a14718c81746daf804b" var GenesisStateCommitment flow.StateCommitment @@ -87,10 +87,10 @@ func genesisCommitHexByChainID(chainID flow.ChainID) string { return GenesisStateCommitmentHex } if chainID == flow.Testnet { - return "d758711a98ebb0cb16023f8b14dfd18ab6b4292af6634f115f47b2065b839f4b" + return "838452ac649d949992e5fb0da61fdba9cfeb09530e27e259b3b24300d3a7687f" } if chainID == flow.Sandboxnet { return "e1c08b17f9e5896f03fe28dd37ca396c19b26628161506924fbf785834646ea1" } - return "a3af0430b178103671b02564a1cc3477ab25acf459523449640a2b6198bd83c8" + return "5164cee75a6f5795a77f52bdf6eb05f47162ddd2a54af97ac30dd3068e6c2b5c" }