diff --git a/pkg/ride/diff_state.go b/pkg/ride/diff_state.go index 42c7f3aaf8..e084d10642 100644 --- a/pkg/ride/diff_state.go +++ b/pkg/ride/diff_state.go @@ -4,6 +4,7 @@ import ( "fmt" "iter" + "github.com/ccoveille/go-safecast/v2" "github.com/pkg/errors" "github.com/wavesplatform/gowaves/pkg/crypto" @@ -32,6 +33,7 @@ type diffBalance struct { balance int64 leaseIn int64 leaseOut int64 + deposit int64 stateGenerating int64 challenged bool } @@ -68,7 +70,7 @@ func (db *diffBalance) spendableBalance() (int64, error) { if err != nil { return 0, err } - return b, nil + return common.SubInt(b, db.deposit) } func (db *diffBalance) checkedRegularBalance() (uint64, error) { @@ -79,7 +81,7 @@ func (db *diffBalance) checkedRegularBalance() (uint64, error) { } func (db *diffBalance) checkedSpendableBalance() (uint64, error) { - b, err := common.SubInt(db.balance, db.leaseOut) + b, err := db.spendableBalance() if err != nil { return 0, err } @@ -101,7 +103,7 @@ func (db *diffBalance) effectiveBalance() (int64, error) { if err != nil { return 0, err } - return v2, nil + return common.SubInt(v2, db.deposit) } func (db *diffBalance) toFullWavesBalance(lightNodeActivated bool) (*proto.FullWavesBalance, error) { @@ -257,10 +259,15 @@ func (ds *diffState) loadWavesBalance(id proto.AddressID) (diffBalance, error) { if err != nil { return diffBalance{}, errors.Wrap(err, "failed to get full Waves balance from state") } + deposit, err := safecast.Convert[int64](profile.Deposit) + if err != nil { + return diffBalance{}, errors.Wrap(err, "failed to convert deposit to int64") + } diff := diffBalance{ balance: int64(profile.Balance), leaseIn: profile.LeaseIn, leaseOut: profile.LeaseOut, + deposit: deposit, stateGenerating: int64(profile.Generating), challenged: profile.Challenged, } diff --git a/pkg/ride/diff_state_test.go b/pkg/ride/diff_state_test.go index 37cc1b396e..06346853ea 100644 --- a/pkg/ride/diff_state_test.go +++ b/pkg/ride/diff_state_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/wavesplatform/gowaves/pkg/crypto" @@ -198,3 +199,79 @@ func TestErrorOnDuplicateLeasing(t *testing.T) { assert.EqualError(t, err3, "lease with id '8N6F4oV2SmfWZ45xVNLQr2rjHyvDWNz8R3wxJzE83ZHm' already exists in ride execution diff") } + +func TestDiffBalanceToFullWavesBalanceTakesDepositIntoAccount(t *testing.T) { + tests := []struct { + name string + lightNodeActivated bool + expectedGenerating uint64 + }{ + { + name: "before Light Node activation", + lightNodeActivated: false, + expectedGenerating: 900, + }, + { + name: "after Light Node activation", + lightNodeActivated: true, + expectedGenerating: 800, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + balance := diffBalance{ + balance: 1_000, + leaseIn: 200, + leaseOut: 100, + deposit: 300, + stateGenerating: 900, + } + + actual, err := balance.toFullWavesBalance(test.lightNodeActivated) + require.NoError(t, err) + require.Equal(t, &proto.FullWavesBalance{ + Regular: 1_000, + Generating: test.expectedGenerating, + Available: 600, + Effective: 800, + LeaseIn: 200, + LeaseOut: 100, + }, actual) + }) + } +} + +func TestDiffStateLoadsDepositFromWavesBalanceProfile(t *testing.T) { + m := types.NewMockEnrichedSmartState(t) + m.EXPECT().WavesBalanceProfile(validAddress.ID()).Return( + &types.WavesBalanceProfile{ + Balance: 1_000, + Deposit: 300, + }, nil, + ).Once() + diff := newDiffState(m) + + actual, err := diff.loadWavesBalance(validAddress.ID()) + require.NoError(t, err) + require.Equal(t, int64(300), actual.deposit) + effective, err := actual.effectiveBalance() + require.NoError(t, err) + require.Equal(t, int64(700), effective) +} + +func TestValidateChangedWavesBalancesTakesDepositIntoAccount(t *testing.T) { + balances := []changedWavesBalancesProfile{ + { + addrID: validAddress.ID(), + diff: diffBalance{ + balance: 100, + deposit: 101, + }, + }, + } + + err := validateChangedWavesBalancesWithOldBalancesBeforeTx( + proto.TestNetScheme, balances, balances, nil, + ) + require.ErrorContains(t, err, "negative scala-like effective balance -1") +} diff --git a/pkg/ride/environment.go b/pkg/ride/environment.go index 235b6ac689..7ad6401e7f 100644 --- a/pkg/ride/environment.go +++ b/pkg/ride/environment.go @@ -1202,7 +1202,7 @@ func validateChangedWavesBalancesWithOldBalancesBeforeTx( } var ( wavesAfter = changedBalance.balance // regular balance - wavesWithoutDepositAfter = wavesAfter // TODO: need to take deposit into account + wavesWithoutDepositAfter = wavesAfter - changedBalance.deposit ) var ( currentLeaseIn = changedBalance.leaseIn @@ -1225,17 +1225,26 @@ func validateChangedWavesBalancesWithOldBalancesBeforeTx( scalaLikeEffectiveBalance, addr.String(), leaseBalanceChangedAtTheLastLayer, - formStateChangesStringPartForErr(oldBalance.balance, oldBalance.leaseOut, oldBalance.leaseIn), - formStateChangesStringPartForErr(wavesAfter, currentLeaseOut, currentLeaseIn), + formStateChangesStringPartForErr( + oldBalance.balance, oldBalance.leaseOut, oldBalance.leaseIn, oldBalance.deposit, + ), + formStateChangesStringPartForErr( + wavesAfter, currentLeaseOut, currentLeaseIn, changedBalance.deposit, + ), ) } } return nil } -func formStateChangesStringPartForErr(wavesRegularBalance int64, leaseOut int64, leaseIn int64) string { - return fmt.Sprintf("(spendable=%d waves=%d leaseOut=%d leaseIn=%d)", // TODO: add deposit - wavesRegularBalance-leaseOut, wavesRegularBalance, leaseOut, leaseIn, +func formStateChangesStringPartForErr(wavesRegularBalance, leaseOut, leaseIn, deposit int64) string { + if deposit == 0 { + return fmt.Sprintf("(spendable=%d waves=%d leaseOut=%d leaseIn=%d)", + wavesRegularBalance-leaseOut, wavesRegularBalance, leaseOut, leaseIn, + ) + } + return fmt.Sprintf("(spendable=%d waves=%d leaseOut=%d leaseIn=%d deposit=%d)", + wavesRegularBalance-leaseOut-deposit, wavesRegularBalance, leaseOut, leaseIn, deposit, ) } diff --git a/pkg/ride/test_helpers_test.go b/pkg/ride/test_helpers_test.go index befd266383..635c2ed0cb 100644 --- a/pkg/ride/test_helpers_test.go +++ b/pkg/ride/test_helpers_test.go @@ -285,22 +285,7 @@ func newTestEnv(t *testing.T) *testEnv { return nil, err } if profile, ok := r.waves[addr]; ok { - eff := int64(profile.Balance) + profile.LeaseIn - profile.LeaseOut - if eff < 0 { - return nil, errors.New("negative effective balance") - } - spb := int64(profile.Balance) - profile.LeaseOut - if spb < 0 { - return nil, errors.New("negative spendable balance") - } - return &proto.FullWavesBalance{ - Regular: profile.Balance, - Generating: profile.Generating, - Available: uint64(spb), - Effective: uint64(eff), - LeaseIn: uint64(profile.LeaseIn), - LeaseOut: uint64(profile.LeaseOut), - }, nil + return profile.ToFullWavesBalance() } return nil, errors.Errorf("no balance profile for address '%s'", addr.String()) }).Maybe() diff --git a/pkg/state/state.go b/pkg/state/state.go index b845bf0936..89d024daec 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -1211,7 +1211,6 @@ func (s *stateManager) FullWavesBalance(account proto.Recipient) (*proto.FullWav Effective: effective, LeaseIn: profile.LeaseInAsUint64(), LeaseOut: profile.LeaseOutAsUint64(), - //TODO: Add Deposit to the profile. }, nil } @@ -1271,9 +1270,9 @@ func (s *stateManager) WavesBalanceProfile(id proto.AddressID) (*types.WavesBala Balance: profile.Balance, LeaseIn: profile.LeaseIn, LeaseOut: profile.LeaseOut, + Deposit: profile.Deposit, Generating: generating, Challenged: challenged, - //TODO: Add Deposit to the profile. }, nil } diff --git a/pkg/state/state_test.go b/pkg/state/state_test.go index 64e228aa28..f0c44e3b13 100644 --- a/pkg/state/state_test.go +++ b/pkg/state/state_test.go @@ -898,6 +898,40 @@ func TestGeneratingBalanceValuesForNewestFunctions(t *testing.T) { }) } +func TestFullWavesBalanceFunctionsTakeDepositIntoAccount(t *testing.T) { + state, testObj := createMockStateManager(t, settings.MustMainNetSettings()) + _, pk, err := crypto.GenerateKeyPair([]byte("full-waves-balance-deposit")) + require.NoError(t, err) + addr, err := proto.NewAddressFromPublicKey(state.settings.AddressSchemeCharacter, pk) + require.NoError(t, err) + + testObj.addBlock(t, blockID0) + testObj.setWavesBalance(t, addr, balanceProfile{ + Balance: 1_000, + LeaseIn: 200, + LeaseOut: 100, + Deposit: 300, + }, blockID0) + testObj.flush(t) + + recipient := proto.NewRecipientFromAddress(addr) + committed, err := state.FullWavesBalance(recipient) + require.NoError(t, err) + newest, err := state.NewestFullWavesBalance(recipient) + require.NoError(t, err) + + for _, balance := range []*proto.FullWavesBalance{committed, newest} { + require.Equal(t, uint64(1_000), balance.Regular) + require.Equal(t, uint64(600), balance.Available) + require.Equal(t, uint64(800), balance.Effective) + require.Equal(t, uint64(200), balance.LeaseIn) + require.Equal(t, uint64(100), balance.LeaseOut) + } + profile, err := state.WavesBalanceProfile(addr.ID()) + require.NoError(t, err) + require.Equal(t, uint64(300), profile.Deposit) +} + type stateForEnv interface { StateInfo types.EnrichedSmartState diff --git a/pkg/types/types.go b/pkg/types/types.go index ea141392a7..c12b6f4898 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -62,6 +62,7 @@ type WavesBalanceProfile struct { Balance uint64 LeaseIn int64 LeaseOut int64 + Deposit uint64 Generating uint64 Challenged bool // if Challenged true, the account considered as challenged at the current height. } @@ -81,14 +82,22 @@ func (bp *WavesBalanceProfile) EffectiveBalance() (uint64, error) { if err != nil { return 0, err } - return common.SubInt(val, uint64(bp.LeaseOut)) + val, err = common.SubInt(val, uint64(bp.LeaseOut)) + if err != nil { + return 0, err + } + return common.SubInt(val, bp.Deposit) } func (bp *WavesBalanceProfile) SpendableBalance() (uint64, error) { if bp.LeaseOut < 0 { return 0, fmt.Errorf("negative lease out balance %d", bp.LeaseOut) } - return common.SubInt(bp.Balance, uint64(bp.LeaseOut)) + val, err := common.SubInt(bp.Balance, uint64(bp.LeaseOut)) + if err != nil { + return 0, err + } + return common.SubInt(val, bp.Deposit) } func (bp *WavesBalanceProfile) ToFullWavesBalance() (*proto.FullWavesBalance, error) { diff --git a/pkg/types/types_test.go b/pkg/types/types_test.go new file mode 100644 index 0000000000..ffab0fe5e5 --- /dev/null +++ b/pkg/types/types_test.go @@ -0,0 +1,55 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/wavesplatform/gowaves/pkg/proto" + "github.com/wavesplatform/gowaves/pkg/types" +) + +func TestWavesBalanceProfileToFullWavesBalanceTakesDepositIntoAccount(t *testing.T) { + profile := &types.WavesBalanceProfile{ + Balance: 1_000, + LeaseIn: 200, + LeaseOut: 100, + Deposit: 300, + Generating: 700, + } + + actual, err := profile.ToFullWavesBalance() + require.NoError(t, err) + require.Equal(t, &proto.FullWavesBalance{ + Regular: 1_000, + Generating: 700, + Available: 600, + Effective: 800, + LeaseIn: 200, + LeaseOut: 100, + }, actual) +} + +func TestWavesBalanceProfileDepositCanMakeBalancesNegative(t *testing.T) { + profile := &types.WavesBalanceProfile{ + Balance: 100, + Deposit: 101, + } + + _, err := profile.SpendableBalance() + require.Error(t, err) + _, err = profile.EffectiveBalance() + require.Error(t, err) +} + +func TestWavesBalanceProfileChallengedEffectiveBalance(t *testing.T) { + profile := &types.WavesBalanceProfile{ + Balance: 1_000, + Deposit: 300, + Challenged: true, + } + + effective, err := profile.EffectiveBalance() + require.NoError(t, err) + require.Zero(t, effective) +}