Compare commits
13 Commits
v1.4.0-rc
...
sync-flags
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd8631eb05
|
||
|
|
c51fd89ad5
|
||
| bcccd79e70 | |||
|
|
193c9c119a
|
||
|
|
36a3c59287
|
||
|
|
1234723e10
|
||
|
|
61f48abb7d
|
||
|
|
3e40a09d39
|
||
|
|
7e90124199
|
||
|
|
e27905765c
|
||
|
|
4ce9baa379
|
||
|
|
e47415cc22
|
||
|
|
1f4d810e14
|
@@ -83,6 +83,7 @@ func (h *MenuHandlers) CheckAccountCreated(ctx context.Context, sym string, inpu
|
|||||||
// CheckBlockedStatus:
|
// CheckBlockedStatus:
|
||||||
// 1. Checks whether the DATA_SELF_PIN_RESET is 1 and sets the flag_account_pin_reset
|
// 1. Checks whether the DATA_SELF_PIN_RESET is 1 and sets the flag_account_pin_reset
|
||||||
// 2. resets the account blocked flag if the PIN attempts have been reset by an admin.
|
// 2. resets the account blocked flag if the PIN attempts have been reset by an admin.
|
||||||
|
// 3. Sets key flags (language and PIN) if the data exists
|
||||||
func (h *MenuHandlers) CheckBlockedStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
func (h *MenuHandlers) CheckBlockedStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
var res resource.Result
|
var res resource.Result
|
||||||
store := h.userdataStore
|
store := h.userdataStore
|
||||||
@@ -90,11 +91,30 @@ func (h *MenuHandlers) CheckBlockedStatus(ctx context.Context, sym string, input
|
|||||||
flag_account_blocked, _ := h.flagManager.GetFlag("flag_account_blocked")
|
flag_account_blocked, _ := h.flagManager.GetFlag("flag_account_blocked")
|
||||||
flag_account_pin_reset, _ := h.flagManager.GetFlag("flag_account_pin_reset")
|
flag_account_pin_reset, _ := h.flagManager.GetFlag("flag_account_pin_reset")
|
||||||
|
|
||||||
|
flag_pin_set, _ := h.flagManager.GetFlag("flag_pin_set")
|
||||||
|
flag_language_set, _ := h.flagManager.GetFlag("flag_language_set")
|
||||||
|
pinFlagSet := h.st.MatchFlag(flag_pin_set, true)
|
||||||
|
languageFlagSet := h.st.MatchFlag(flag_language_set, true)
|
||||||
|
|
||||||
sessionId, ok := ctx.Value("SessionId").(string)
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return res, fmt.Errorf("missing session")
|
return res, fmt.Errorf("missing session")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only check the data if the flag isn't set
|
||||||
|
if !pinFlagSet {
|
||||||
|
accountPin, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN)
|
||||||
|
if len(accountPin) > 0 {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_pin_set)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !languageFlagSet {
|
||||||
|
languageCode, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_SELECTED_LANGUAGE_CODE)
|
||||||
|
if len(languageCode) > 0 {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_language_set)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
res.FlagReset = append(res.FlagReset, flag_account_pin_reset)
|
res.FlagReset = append(res.FlagReset, flag_account_pin_reset)
|
||||||
|
|
||||||
selfPinReset, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SELF_PIN_RESET)
|
selfPinReset, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SELF_PIN_RESET)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.defalsify.org/vise.git/resource"
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.defalsify.org/vise.git/state"
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-api/models"
|
"git.grassecon.net/grassrootseconomics/sarafu-api/models"
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks"
|
"git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks"
|
||||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
@@ -93,16 +94,24 @@ func TestCheckBlockedStatus(t *testing.T) {
|
|||||||
}
|
}
|
||||||
flag_account_blocked, _ := fm.GetFlag("flag_account_blocked")
|
flag_account_blocked, _ := fm.GetFlag("flag_account_blocked")
|
||||||
flag_account_pin_reset, _ := fm.GetFlag("flag_account_pin_reset")
|
flag_account_pin_reset, _ := fm.GetFlag("flag_account_pin_reset")
|
||||||
|
flag_pin_set, _ := fm.GetFlag("flag_pin_set")
|
||||||
|
flag_language_set, _ := fm.GetFlag("flag_language_set")
|
||||||
|
|
||||||
|
// Set the flag in the State
|
||||||
|
mockState := state.NewState(128)
|
||||||
|
|
||||||
h := &MenuHandlers{
|
h := &MenuHandlers{
|
||||||
userdataStore: store,
|
userdataStore: store,
|
||||||
flagManager: fm,
|
flagManager: fm,
|
||||||
|
st: mockState,
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
currentWrongPinAttempts string
|
currentWrongPinAttempts string
|
||||||
expectedResult resource.Result
|
expectedResult resource.Result
|
||||||
|
languageSet bool
|
||||||
|
PinSet bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Currently blocked account",
|
name: "Currently blocked account",
|
||||||
@@ -118,6 +127,16 @@ func TestCheckBlockedStatus(t *testing.T) {
|
|||||||
FlagReset: []uint32{flag_account_pin_reset, flag_account_blocked},
|
FlagReset: []uint32{flag_account_pin_reset, flag_account_blocked},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Valid account with reset language and PIN flags",
|
||||||
|
currentWrongPinAttempts: "0",
|
||||||
|
languageSet: true,
|
||||||
|
PinSet: true,
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_account_pin_reset, flag_account_blocked},
|
||||||
|
FlagSet: []uint32{flag_pin_set, flag_language_set},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -126,6 +145,18 @@ func TestCheckBlockedStatus(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tt.languageSet {
|
||||||
|
if err := store.WriteEntry(ctx, sessionId, storedb.DATA_SELECTED_LANGUAGE_CODE, []byte("eng")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.PinSet {
|
||||||
|
if err := store.WriteEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte("hasedPinValue")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
res, err := h.CheckBlockedStatus(ctx, "", []byte(""))
|
res, err := h.CheckBlockedStatus(ctx, "", []byte(""))
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||||
"gopkg.in/leonelquinteros/gotext.v1"
|
"gopkg.in/leonelquinteros/gotext.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,9 +37,26 @@ func (h *MenuHandlers) GetPools(ctx context.Context, sym string, input []byte) (
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
data := store.ProcessPools(topPools)
|
activePoolSymStr := ""
|
||||||
|
|
||||||
// Store all Pool data
|
activePoolSym, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_SYM)
|
||||||
|
if err != nil {
|
||||||
|
activePoolSymStr = config.DefaultPoolSymbol()
|
||||||
|
} else {
|
||||||
|
activePoolSymStr = string(activePoolSym)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out the active pool from topPools
|
||||||
|
filteredPools := make([]dataserviceapi.PoolDetails, 0, len(topPools))
|
||||||
|
for _, p := range topPools {
|
||||||
|
if p.PoolSymbol != activePoolSymStr {
|
||||||
|
filteredPools = append(filteredPools, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data := store.ProcessPools(filteredPools)
|
||||||
|
|
||||||
|
// Store the filtered Pool data
|
||||||
dataMap := map[storedb.DataTyp]string{
|
dataMap := map[storedb.DataTyp]string{
|
||||||
storedb.DATA_POOL_NAMES: data.PoolNames,
|
storedb.DATA_POOL_NAMES: data.PoolNames,
|
||||||
storedb.DATA_POOL_SYMBOLS: data.PoolSymbols,
|
storedb.DATA_POOL_SYMBOLS: data.PoolSymbols,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||||
"gopkg.in/leonelquinteros/gotext.v1"
|
"gopkg.in/leonelquinteros/gotext.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -121,7 +122,16 @@ func (h *MenuHandlers) LoadSwapToList(ctx context.Context, sym string, input []b
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
data := store.ProcessVouchers(swapToList)
|
// Filter out the active voucher from swapToList
|
||||||
|
filteredSwapToList := make([]dataserviceapi.TokenHoldings, 0, len(swapToList))
|
||||||
|
for _, s := range swapToList {
|
||||||
|
if s.TokenSymbol != string(activeSym) {
|
||||||
|
filteredSwapToList = append(filteredSwapToList, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store filtered swap to list data (excluding the current active voucher)
|
||||||
|
data := store.ProcessVouchers(filteredSwapToList)
|
||||||
|
|
||||||
logg.InfoCtxf(ctx, "ProcessVouchers", "data", data)
|
logg.InfoCtxf(ctx, "ProcessVouchers", "data", data)
|
||||||
|
|
||||||
@@ -161,7 +171,7 @@ func (h *MenuHandlers) SwapMaxLimit(ctx context.Context, sym string, input []byt
|
|||||||
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher, flag_low_swap_amount)
|
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher, flag_low_swap_amount)
|
||||||
|
|
||||||
inputStr := string(input)
|
inputStr := string(input)
|
||||||
if inputStr == "0" {
|
if inputStr == "0" || inputStr == "99" || inputStr == "88" || inputStr == "98" {
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ func (h *MenuHandlers) ManageVouchers(ctx context.Context, sym string, input []b
|
|||||||
|
|
||||||
res.FlagReset = append(res.FlagReset, flag_no_active_voucher)
|
res.FlagReset = append(res.FlagReset, flag_no_active_voucher)
|
||||||
|
|
||||||
|
// add a variable to filter out the active voucher
|
||||||
|
activeSymStr := ""
|
||||||
|
|
||||||
// Check if user has an active voucher with proper error handling
|
// Check if user has an active voucher with proper error handling
|
||||||
activeSym, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM)
|
activeSym, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -62,6 +65,8 @@ func (h *MenuHandlers) ManageVouchers(ctx context.Context, sym string, input []b
|
|||||||
defaultDec := firstVoucher.TokenDecimals
|
defaultDec := firstVoucher.TokenDecimals
|
||||||
defaultAddr := firstVoucher.TokenAddress
|
defaultAddr := firstVoucher.TokenAddress
|
||||||
|
|
||||||
|
activeSymStr = defaultSym
|
||||||
|
|
||||||
// Scale down the balance
|
// Scale down the balance
|
||||||
scaledBalance := store.ScaleDownBalance(defaultBal, defaultDec)
|
scaledBalance := store.ScaleDownBalance(defaultBal, defaultDec)
|
||||||
|
|
||||||
@@ -89,10 +94,8 @@ func (h *MenuHandlers) ManageVouchers(ctx context.Context, sym string, input []b
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Update active voucher balance
|
|
||||||
activeSymStr := string(activeSym)
|
|
||||||
|
|
||||||
// Find the matching voucher data
|
// Find the matching voucher data
|
||||||
|
activeSymStr = string(activeSym)
|
||||||
var activeData *dataserviceapi.TokenHoldings
|
var activeData *dataserviceapi.TokenHoldings
|
||||||
for _, voucher := range vouchersResp {
|
for _, voucher := range vouchersResp {
|
||||||
if voucher.TokenSymbol == activeSymStr {
|
if voucher.TokenSymbol == activeSymStr {
|
||||||
@@ -102,9 +105,10 @@ func (h *MenuHandlers) ManageVouchers(ctx context.Context, sym string, input []b
|
|||||||
}
|
}
|
||||||
|
|
||||||
if activeData == nil {
|
if activeData == nil {
|
||||||
logg.ErrorCtxf(ctx, "activeSym not found in vouchers, setting the first voucher as the default", "activeSym", activeSymStr)
|
logg.InfoCtxf(ctx, "activeSym not found in vouchers, setting the first voucher as the default", "activeSym", activeSymStr)
|
||||||
firstVoucher := vouchersResp[0]
|
firstVoucher := vouchersResp[0]
|
||||||
activeData = &firstVoucher
|
activeData = &firstVoucher
|
||||||
|
activeSymStr = string(activeData.TokenSymbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scale down the balance
|
// Scale down the balance
|
||||||
@@ -120,8 +124,17 @@ func (h *MenuHandlers) ManageVouchers(ctx context.Context, sym string, input []b
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store all voucher data
|
// Filter out the active voucher from vouchersResp
|
||||||
data := store.ProcessVouchers(vouchersResp)
|
filteredVouchers := make([]dataserviceapi.TokenHoldings, 0, len(vouchersResp))
|
||||||
|
for _, v := range vouchersResp {
|
||||||
|
if v.TokenSymbol != activeSymStr {
|
||||||
|
filteredVouchers = append(filteredVouchers, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store all voucher data (excluding the current active voucher)
|
||||||
|
data := store.ProcessVouchers(filteredVouchers)
|
||||||
|
|
||||||
dataMap := map[storedb.DataTyp]string{
|
dataMap := map[storedb.DataTyp]string{
|
||||||
storedb.DATA_VOUCHER_SYMBOLS: data.Symbols,
|
storedb.DATA_VOUCHER_SYMBOLS: data.Symbols,
|
||||||
storedb.DATA_VOUCHER_BALANCES: data.Balances,
|
storedb.DATA_VOUCHER_BALANCES: data.Balances,
|
||||||
@@ -140,7 +153,7 @@ func (h *MenuHandlers) ManageVouchers(ctx context.Context, sym string, input []b
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVoucherList fetches the list of vouchers and formats them.
|
// GetVoucherList fetches the list of vouchers from the store and formats them.
|
||||||
func (h *MenuHandlers) GetVoucherList(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
func (h *MenuHandlers) GetVoucherList(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
var res resource.Result
|
var res resource.Result
|
||||||
sessionId, ok := ctx.Value("SessionId").(string)
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
@@ -191,6 +204,10 @@ func (h *MenuHandlers) ViewVoucher(ctx context.Context, sym string, input []byte
|
|||||||
flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher")
|
flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher")
|
||||||
|
|
||||||
inputStr := string(input)
|
inputStr := string(input)
|
||||||
|
if inputStr == "0" || inputStr == "99" || inputStr == "88" || inputStr == "98" {
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
metadata, err := store.GetVoucherData(ctx, h.userdataStore, sessionId, inputStr)
|
metadata, err := store.GetVoucherData(ctx, h.userdataStore, sessionId, inputStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -207,8 +224,16 @@ func (h *MenuHandlers) ViewVoucher(ctx context.Context, sym string, input []byte
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Format the balance to 2 decimal places
|
||||||
|
formattedAmount, err := store.TruncateDecimalString(metadata.Balance, 2)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to TruncateDecimalString on ViewVoucher", "error", err)
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_incorrect_voucher)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
|
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
|
||||||
res.Content = l.Get("Symbol: %s\nBalance: %s", metadata.TokenSymbol, metadata.Balance)
|
res.Content = l.Get("Symbol: %s\nBalance: %s", metadata.TokenSymbol, formattedAmount)
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,8 +73,8 @@ func TestManageVouchers(t *testing.T) {
|
|||||||
Balance: "100",
|
Balance: "100",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedVoucherSymbols: []byte("1:TOKEN1"),
|
expectedVoucherSymbols: []byte(""),
|
||||||
expectedUpdatedAddress: []byte("0x123"),
|
expectedUpdatedAddress: []byte(""),
|
||||||
expectedResult: resource.Result{
|
expectedResult: resource.Result{
|
||||||
FlagReset: []uint32{flag_api_error, flag_no_active_voucher},
|
FlagReset: []uint32{flag_api_error, flag_no_active_voucher},
|
||||||
},
|
},
|
||||||
@@ -86,7 +86,7 @@ func TestManageVouchers(t *testing.T) {
|
|||||||
{TokenAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"},
|
{TokenAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"},
|
||||||
},
|
},
|
||||||
storedActiveVoucher: "SRF",
|
storedActiveVoucher: "SRF",
|
||||||
expectedVoucherSymbols: []byte("1:SRF\n2:MILO"),
|
expectedVoucherSymbols: []byte("1:MILO"),
|
||||||
expectedUpdatedAddress: []byte("0xd4c288865Ce"),
|
expectedUpdatedAddress: []byte("0xd4c288865Ce"),
|
||||||
expectedResult: resource.Result{
|
expectedResult: resource.Result{
|
||||||
FlagReset: []uint32{flag_api_error, flag_no_active_voucher},
|
FlagReset: []uint32{flag_api_error, flag_no_active_voucher},
|
||||||
|
|||||||
@@ -4,11 +4,17 @@ RELOAD swap_to_list
|
|||||||
MAP swap_to_list
|
MAP swap_to_list
|
||||||
CATCH missing_voucher flag_incorrect_voucher 1
|
CATCH missing_voucher flag_incorrect_voucher 1
|
||||||
MOUT back 0
|
MOUT back 0
|
||||||
|
MOUT quit 99
|
||||||
|
MNEXT next 88
|
||||||
|
MPREV prev 98
|
||||||
HALT
|
HALT
|
||||||
|
INCMP > 88
|
||||||
|
INCMP < 98
|
||||||
|
INCMP _ 0
|
||||||
|
INCMP quit 99
|
||||||
LOAD swap_max_limit 64
|
LOAD swap_max_limit 64
|
||||||
RELOAD swap_max_limit
|
RELOAD swap_max_limit
|
||||||
CATCH api_failure flag_api_call_error 1
|
CATCH api_failure flag_api_call_error 1
|
||||||
CATCH . flag_incorrect_voucher 1
|
CATCH . flag_incorrect_voucher 1
|
||||||
CATCH low_swap_amount flag_low_swap_amount 1
|
CATCH low_swap_amount flag_low_swap_amount 1
|
||||||
INCMP _ 0
|
|
||||||
INCMP swap_limit *
|
INCMP swap_limit *
|
||||||
|
|||||||
Reference in New Issue
Block a user