diff --git a/pkg/ride/diff_state.go b/pkg/ride/diff_state.go index 750ce5de5..997394ea4 100644 --- a/pkg/ride/diff_state.go +++ b/pkg/ride/diff_state.go @@ -103,7 +103,7 @@ func (db *diffBalance) effectiveBalance() (int64, error) { return v2, nil } -func (db *diffBalance) toFullWavesBalance() (*proto.FullWavesBalance, error) { +func (db *diffBalance) toFullWavesBalance(lightNodeActivated bool) (*proto.FullWavesBalance, error) { eff, err := db.effectiveBalance() if err != nil { return nil, err @@ -118,9 +118,13 @@ func (db *diffBalance) toFullWavesBalance() (*proto.FullWavesBalance, error) { if spb < 0 { return nil, errors.New("negative spendable balance") } - gen := eff - if db.stateGenerating < gen { - gen = db.stateGenerating + // According to the scala node implementation: + // Before Light Node activation generating balance doesn't change during the script execution. + // Update generating balance if it's greater than effective balance, i.e. update generating balance during + // script processing. + gen := db.stateGenerating + if lightNodeActivated && eff < gen { + gen = eff } return &proto.FullWavesBalance{ Regular: uint64(db.balance), diff --git a/pkg/ride/environment.go b/pkg/ride/environment.go index 875e53f04..0d9eaf991 100644 --- a/pkg/ride/environment.go +++ b/pkg/ride/environment.go @@ -28,20 +28,22 @@ type WrappedState struct { dataEntriesSize int rootScriptLibVersion ast.LibraryVersion rootActionsCountValidator proto.ActionsCountValidator + isLightNodeActivated bool } func newWrappedState( - env *EvaluationEnvironment, + env environment, originalStateForWrappedState types.EnrichedSmartState, rootScriptLibVersion ast.LibraryVersion, ) *WrappedState { return &WrappedState{ diff: newDiffState(originalStateForWrappedState), - cle: env.th.(rideAddress), - scheme: env.sch, + cle: env.this().(rideAddress), + scheme: env.scheme(), height: proto.Height(env.height()), rootScriptLibVersion: rootScriptLibVersion, rootActionsCountValidator: proto.NewScriptActionsCountValidator(), + isLightNodeActivated: env.lightNodeActivated(), } } @@ -157,7 +159,7 @@ func (ws *WrappedState) NewestFullWavesBalance(account proto.Recipient) (*proto. if err != nil { return nil, err } - return b.toFullWavesBalance() + return b.toFullWavesBalance(ws.isLightNodeActivated) } func (ws *WrappedState) IsStateUntouched(account proto.Recipient) (bool, error) { diff --git a/pkg/ride/test_helpers_test.go b/pkg/ride/test_helpers_test.go index 32d003798..ba0242e64 100644 --- a/pkg/ride/test_helpers_test.go +++ b/pkg/ride/test_helpers_test.go @@ -635,16 +635,12 @@ func (e *testEnv) withDataFromJSON(s string) *testEnv { func (e *testEnv) withWrappedState() *testEnv { v, err := e.me.libVersion() - if err != nil { - panic(err) - } - e.ws = &WrappedState{ - diff: newDiffState(e.ms), - cle: e.me.this().(rideAddress), - scheme: e.me.scheme(), - rootScriptLibVersion: v, - rootActionsCountValidator: proto.NewScriptActionsCountValidator(), + require.NoError(e.t, err) + if e.me.heightFunc == nil { // create stub height function` + e.me.heightFunc = func() rideInt { return 0 } + defer func() { e.me.heightFunc = nil }() } + e.ws = newWrappedState(e.me, e.ms, v) e.me.stateFunc = func() types.SmartState { return e.ws } diff --git a/pkg/ride/tree_evaluation_test.go b/pkg/ride/tree_evaluation_test.go index 8c6fa1b0a..66c03d038 100644 --- a/pkg/ride/tree_evaluation_test.go +++ b/pkg/ride/tree_evaluation_test.go @@ -6330,3 +6330,81 @@ func TestAttachedPaymentsValidation(t *testing.T) { }) }) } + +func TestUpdateGeneratingBalanceDuringScriptExecutionAfterLightNode(t *testing.T) { + sender := newTestAccount(t, "SENDER") // 3N8CkZAyS4XcDoJTJoKNuNk2xmNKmQj7myW + dApp1 := newTestAccount(t, "DAPP1") // 3MzDtgL5yw73C2xVLnLJCrT5gCL4357a4sz + + const src = ` + {-# STDLIB_VERSION 5 #-} + {-# CONTENT_TYPE DAPP #-} + {-# SCRIPT_TYPE ACCOUNT #-} + + @Callable(i) + func call(to: String, value: Int) = { + let balanceBeforeTransfer = wavesBalance(this) + let balanceAfter = invoke(this, "transfer", [to, value], []) + let balanceAfterTransfer = wavesBalance(this) + let isUpdated = balanceAfter == balanceAfterTransfer.generating + if (!isUpdated && balanceBeforeTransfer.generating != balanceAfterTransfer.generating) then + throw("failure") + else + ([], isUpdated) + } + + @Callable(i) + func transfer(to: String, value: Int) = { + let balance = wavesBalance(this) + let toAddr = addressFromStringValue(to) + ([ScriptTransfer(toAddr, value, unit)], balance.generating - value) + }` + tree, errs := ridec.CompileToTree(src) + require.Empty(t, errs) + + const ( + dAppInitialBalance = 10 * proto.PriceConstant + transferAmount = 5 * proto.PriceConstant + ) + + createEnv := func(t *testing.T, lightNodeActivated bool) *testEnv { + complexity, err := MaxChainInvokeComplexityByVersion(ast.LibV5) + require.NoError(t, err) + env := newTestEnv(t).withLibVersion(ast.LibV5).withComplexityLimit(int(complexity)). + withBlockV5Activated().withProtobufTx(). + withDataEntriesSizeV2().withMessageLengthV3().withValidateInternalPayments(). + withThis(dApp1).withDApp(dApp1).withSender(sender). + withInvocation("call", withTransactionID(crypto.Digest{})).withTree(dApp1, tree). + withWavesBalance(sender, 0). + withWavesBalance(dApp1, dAppInitialBalance, 0, 0, dAppInitialBalance) + if lightNodeActivated { + env = env.withLightNodeActivated() + } + return env.withWrappedState() + } + t.Run("before-light-node", func(t *testing.T) { + env := createEnv(t, false).toEnv() + fc := proto.NewFunctionCall("call", proto.Arguments{ + proto.NewStringArgument(sender.address().String()), + proto.NewIntegerArgument(transferAmount), + }) + res, err := CallFunction(env, tree, fc) + require.NoError(t, err) + require.Len(t, res.ScriptActions(), 1) + isBalanceUpdated, ok := res.userResult().(rideBoolean) + require.True(t, ok) + assert.False(t, bool(isBalanceUpdated)) + }) + t.Run("after-light-node", func(t *testing.T) { + env := createEnv(t, true).toEnv() + fc := proto.NewFunctionCall("call", proto.Arguments{ + proto.NewStringArgument(sender.address().String()), + proto.NewIntegerArgument(transferAmount), + }) + res, err := CallFunction(env, tree, fc) + require.NoError(t, err) + require.Len(t, res.ScriptActions(), 1) + isBalanceUpdated, ok := res.userResult().(rideBoolean) + require.True(t, ok) + assert.True(t, bool(isBalanceUpdated)) + }) +}