diff --git a/cmd/main.go b/cmd/main.go index 7deede9..850dc7d 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -8,7 +8,6 @@ import ( "syscall" "git.defalsify.org/vise.git/db/mem" - "git.grassecon.net/urdt/ussd/initializers" "git.grassecon.net/term/config" "git.grassecon.net/term/event/nats" diff --git a/event/nats/nats_test.go b/event/nats/nats_test.go index 7bdf8e2..766c45b 100644 --- a/event/nats/nats_test.go +++ b/event/nats/nats_test.go @@ -18,6 +18,7 @@ import ( "git.grassecon.net/urdt/ussd/models" "git.grassecon.net/term/lookup" "git.grassecon.net/term/event" + "git.grassecon.net/term/internal/testutil" ) const ( @@ -31,9 +32,6 @@ const ( txTimestamp = 1730592500 txHash = "0xabcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" sinkAddress = "0xb42C5920014eE152F2225285219407938469BBfA" - aliceChecksum = "0xeae046BF396e91f5A8D74f863dC57c107c8a4a70" - bobChecksum = "0xB3117202371853e24B725d4169D87616A7dDb127" - aliceSession = "5553425" ) // TODO: jetstream, would have been nice of you to provide an easier way to make a mock msg @@ -89,39 +87,17 @@ func(m *testMsg) Metadata() (*jetstream.MsgMetadata, error) { return nil, nil } -type mockApi struct { -} +func TestHandleMsg(t *testing.T) { + err := config.LoadConfig() + if err != nil { + t.Fatal(err) + } -func(m mockApi) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResult, error) { - return nil, nil -} - -func(m mockApi) CreateAccount(ctx context.Context) (*models.AccountResult, error) { - return nil, nil -} - -func(m mockApi) TrackAccountStatus(ctx context.Context, publicKey string) (*models.TrackStatusResult, error) { - return nil, nil -} - -func(m mockApi) FetchVouchers(ctx context.Context, publicKey string) ([]dataserviceapi.TokenHoldings, error) { - logg.DebugCtxf(ctx, "mockapi fetchvouchers", "key", publicKey) - return []dataserviceapi.TokenHoldings{ - dataserviceapi.TokenHoldings{ - ContractAddress: tokenAddress, - TokenSymbol: tokenSymbol, - TokenDecimals: strconv.Itoa(tokenDecimals), - Balance: strconv.Itoa(tokenBalance), - }, - }, nil -} - -func(m mockApi) FetchTransactions(ctx context.Context, publicKey string) ([]dataserviceapi.Last10TxResponse, error) { - logg.DebugCtxf(ctx, "mockapi fetchtransactions", "key", publicKey) - return []dataserviceapi.Last10TxResponse{ + api := &testutil.MockApi{} + api.TransactionsContent = []dataserviceapi.Last10TxResponse{ dataserviceapi.Last10TxResponse{ - Sender: aliceChecksum, - Recipient: bobChecksum, + Sender: testutil.AliceChecksum, + Recipient: testutil.BobChecksum, TransferValue: strconv.Itoa(txValue), ContractAddress: tokenAddress, TxHash: txHash, @@ -129,25 +105,22 @@ func(m mockApi) FetchTransactions(ctx context.Context, publicKey string) ([]data TokenSymbol: tokenSymbol, TokenDecimals: strconv.Itoa(tokenDecimals), }, - }, nil -} - -func(m mockApi) VoucherData(ctx context.Context, address string) (*models.VoucherDataResult, error) { - return &models.VoucherDataResult{ + } + api.VoucherDataContent = &models.VoucherDataResult{ TokenSymbol: tokenSymbol, TokenName: tokenName, TokenDecimals: strconv.Itoa(tokenDecimals), SinkAddress: sinkAddress, - }, nil -} - -func TestHandleMsg(t *testing.T) { - err := config.LoadConfig() - if err != nil { - t.Fatal(err) } - - lookup.Api = mockApi{} + api.VouchersContent = []dataserviceapi.TokenHoldings{ + dataserviceapi.TokenHoldings{ + ContractAddress: tokenAddress, + TokenSymbol: tokenSymbol, + TokenDecimals: strconv.Itoa(tokenDecimals), + Balance: strconv.Itoa(tokenBalance), + }, + } + lookup.Api = api ctx := context.Background() userDb := memdb.NewMemDb() @@ -156,18 +129,19 @@ func TestHandleMsg(t *testing.T) { panic(err) } - alice, err := common.NormalizeHex(aliceChecksum) + alice, err := common.NormalizeHex(testutil.AliceChecksum) if err != nil { t.Fatal(err) } userDb.SetSession(alice) userDb.SetPrefix(db.DATATYPE_USERDATA) - err = userDb.Put(ctx, common.PackKey(common.DATA_PUBLIC_KEY_REVERSE, []byte{}), []byte(aliceSession)) + err = userDb.Put(ctx, common.PackKey(common.DATA_PUBLIC_KEY_REVERSE, []byte{}), []byte(testutil.AliceSession)) if err != nil { t.Fatal(err) } + sub := NewNatsSubscription(userDb) data := fmt.Sprintf(`{ @@ -182,7 +156,7 @@ func TestHandleMsg(t *testing.T) { "to": "%s", "value": "%d" } -}`, txBlock, tokenAddress, txTimestamp, txHash, aliceChecksum, bobChecksum, txValue) +}`, txBlock, tokenAddress, txTimestamp, txHash, testutil.AliceChecksum, testutil.BobChecksum, txValue) msg := &testMsg{ data: []byte(data), } @@ -191,7 +165,7 @@ func TestHandleMsg(t *testing.T) { store := common.UserDataStore{ Db: userDb, } - v, err := store.ReadEntry(ctx, aliceSession, common.DATA_ACTIVE_SYM) + v, err := store.ReadEntry(ctx, testutil.AliceSession, common.DATA_ACTIVE_SYM) if err != nil { t.Fatal(err) } @@ -199,7 +173,7 @@ func TestHandleMsg(t *testing.T) { t.Fatalf("expected '%s', got %s", tokenSymbol, v) } - v, err = store.ReadEntry(ctx, aliceSession, common.DATA_ACTIVE_BAL) + v, err = store.ReadEntry(ctx, testutil.AliceSession, common.DATA_ACTIVE_BAL) if err != nil { t.Fatal(err) } @@ -207,7 +181,7 @@ func TestHandleMsg(t *testing.T) { t.Fatalf("expected '%d', got %s", tokenBalance, v) } - v, err = store.ReadEntry(ctx, aliceSession, common.DATA_TRANSACTIONS) + v, err = store.ReadEntry(ctx, testutil.AliceSession, common.DATA_TRANSACTIONS) if err != nil { t.Fatal(err) } @@ -216,7 +190,7 @@ func TestHandleMsg(t *testing.T) { } userDb.SetPrefix(event.DATATYPE_USERSUB) - userDb.SetSession(aliceSession) + userDb.SetSession(testutil.AliceSession) k := append([]byte("vouchers"), []byte("sym")...) v, err = userDb.Get(ctx, k) if err != nil { diff --git a/event/token.go b/event/token.go index 9b94054..1928eae 100644 --- a/event/token.go +++ b/event/token.go @@ -22,13 +22,21 @@ const ( // fields used for handling token transfer event. type eventTokenTransfer struct { + To string + Value int + VoucherAddress string + TxHash string From string +} + +type eventTokenMint struct { To string Value int TxHash string VoucherAddress string } + // formatter for transaction data // // TODO: current formatting is a placeholder. @@ -215,7 +223,7 @@ func handleTokenTransfer(ctx context.Context, store *common.UserDataStore, ev *e } } - if strings.Compare(ev.To, ev.From) { + if strings.Compare(ev.To, ev.From) != 0 { identity, err = lookup.IdentityFromAddress(ctx, store, ev.To) if err != nil { if !db.IsNotFound(err) { @@ -231,3 +239,19 @@ func handleTokenTransfer(ctx context.Context, store *common.UserDataStore, ev *e return nil } + +// handle token mint. +func handleTokenMint(ctx context.Context, store *common.UserDataStore, ev *eventTokenMint) error { + identity, err := lookup.IdentityFromAddress(ctx, store, ev.To) + if err != nil { + if !db.IsNotFound(err) { + return err + } + } else { + err = updateToken(ctx, store, identity, ev.VoucherAddress) + if err != nil { + return err + } + } + return nil +} diff --git a/event/token_test.go b/event/token_test.go new file mode 100644 index 0000000..634ccf2 --- /dev/null +++ b/event/token_test.go @@ -0,0 +1,203 @@ +package event + +import ( + "bytes" + "context" + "fmt" + "strconv" + "testing" + "time" + + dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" + "git.defalsify.org/vise.git/db" + memdb "git.defalsify.org/vise.git/db/mem" + "git.grassecon.net/urdt/ussd/config" + "git.grassecon.net/urdt/ussd/models" + "git.grassecon.net/term/lookup" + "git.grassecon.net/urdt/ussd/common" + "git.grassecon.net/term/internal/testutil" +) + +const ( + txBlock = 42 + tokenAddress = "0x765DE816845861e75A25fCA122bb6898B8B1282a" + tokenSymbol = "FOO" + tokenName = "Foo Token" + tokenDecimals = 6 + txValue = 1337 + tokenBalance = 362436 + txTimestamp = 1730592500 + txHash = "0xabcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" + sinkAddress = "0xb42C5920014eE152F2225285219407938469BBfA" +) + +func TestTokenTransfer(t *testing.T) { + err := config.LoadConfig() + if err != nil { + t.Fatal(err) + } + + api := &testutil.MockApi{} + api.TransactionsContent = []dataserviceapi.Last10TxResponse{ + dataserviceapi.Last10TxResponse{ + Sender: testutil.AliceChecksum, + Recipient: testutil.BobChecksum, + TransferValue: strconv.Itoa(txValue), + ContractAddress: tokenAddress, + TxHash: txHash, + DateBlock: time.Unix(txTimestamp, 0), + TokenSymbol: tokenSymbol, + TokenDecimals: strconv.Itoa(tokenDecimals), + }, + } + api.VoucherDataContent = &models.VoucherDataResult{ + TokenSymbol: tokenSymbol, + TokenName: tokenName, + TokenDecimals: strconv.Itoa(tokenDecimals), + SinkAddress: sinkAddress, + } + api.VouchersContent = []dataserviceapi.TokenHoldings{ + dataserviceapi.TokenHoldings{ + ContractAddress: tokenAddress, + TokenSymbol: tokenSymbol, + TokenDecimals: strconv.Itoa(tokenDecimals), + Balance: strconv.Itoa(tokenBalance), + }, + } + lookup.Api = api + + ctx := context.Background() + userDb := memdb.NewMemDb() + err = userDb.Connect(ctx, "") + if err != nil { + panic(err) + } + + alice, err := common.NormalizeHex(testutil.AliceChecksum) + if err != nil { + t.Fatal(err) + } + + userDb.SetSession(alice) + userDb.SetPrefix(db.DATATYPE_USERDATA) + err = userDb.Put(ctx, common.PackKey(common.DATA_PUBLIC_KEY_REVERSE, []byte{}), []byte(testutil.AliceSession)) + if err != nil { + t.Fatal(err) + } + store := common.UserDataStore{ + Db: userDb, + } + + ev := &eventTokenTransfer{ + From: testutil.BobChecksum, + To: testutil.AliceChecksum, + Value: txValue, + } + err = handleTokenTransfer(ctx, &store, ev) + if err != nil { + t.Fatal(err) + } +} + +func TestTokenMint(t *testing.T) { + err := config.LoadConfig() + if err != nil { + t.Fatal(err) + } + + api := &testutil.MockApi{} + api.TransactionsContent = []dataserviceapi.Last10TxResponse{ + dataserviceapi.Last10TxResponse{ + Sender: testutil.AliceChecksum, + Recipient: testutil.BobChecksum, + TransferValue: strconv.Itoa(txValue), + ContractAddress: tokenAddress, + TxHash: txHash, + DateBlock: time.Unix(txTimestamp, 0), + TokenSymbol: tokenSymbol, + TokenDecimals: strconv.Itoa(tokenDecimals), + }, + } + api.VoucherDataContent = &models.VoucherDataResult{ + TokenSymbol: tokenSymbol, + TokenName: tokenName, + TokenDecimals: strconv.Itoa(tokenDecimals), + SinkAddress: sinkAddress, + } + api.VouchersContent = []dataserviceapi.TokenHoldings{ + dataserviceapi.TokenHoldings{ + ContractAddress: tokenAddress, + TokenSymbol: tokenSymbol, + TokenDecimals: strconv.Itoa(tokenDecimals), + Balance: strconv.Itoa(tokenBalance), + }, + } + lookup.Api = api + + ctx := context.Background() + userDb := memdb.NewMemDb() + err = userDb.Connect(ctx, "") + if err != nil { + panic(err) + } + + alice, err := common.NormalizeHex(testutil.AliceChecksum) + if err != nil { + t.Fatal(err) + } + + userDb.SetSession(alice) + userDb.SetPrefix(db.DATATYPE_USERDATA) + err = userDb.Put(ctx, common.PackKey(common.DATA_PUBLIC_KEY_REVERSE, []byte{}), []byte(testutil.AliceSession)) + if err != nil { + t.Fatal(err) + } + store := common.UserDataStore{ + Db: userDb, + } + + ev := &eventTokenMint{ + To: testutil.AliceChecksum, + Value: txValue, + } + err = handleTokenMint(ctx, &store, ev) + if err != nil { + t.Fatal(err) + } + + v, err := store.ReadEntry(ctx, testutil.AliceSession, common.DATA_ACTIVE_SYM) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(v, []byte(tokenSymbol)) { + t.Fatalf("expected '%s', got %s", tokenSymbol, v) + } + + v, err = store.ReadEntry(ctx, testutil.AliceSession, common.DATA_ACTIVE_BAL) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(v, []byte(strconv.Itoa(tokenBalance))) { + t.Fatalf("expected '%d', got %s", tokenBalance, v) + } + + v, err = store.ReadEntry(ctx, testutil.AliceSession, common.DATA_TRANSACTIONS) + if err != nil { + t.Fatal(err) + } + if !bytes.Contains(v, []byte("abcdef")) { + t.Fatal("no transaction data") + } + + userDb.SetPrefix(DATATYPE_USERSUB) + userDb.SetSession(testutil.AliceSession) + k := append([]byte("vouchers"), []byte("sym")...) + v, err = userDb.Get(ctx, k) + if err != nil { + t.Fatal(err) + } + if !bytes.Contains(v, []byte(fmt.Sprintf("1:%s", tokenSymbol))) { + t.Fatalf("expected '1:%s', got %s", tokenSymbol, v) + } + +} diff --git a/internal/testutil/api.go b/internal/testutil/api.go new file mode 100644 index 0000000..62a0fe3 --- /dev/null +++ b/internal/testutil/api.go @@ -0,0 +1,51 @@ +package testutil + +import ( + "context" + + "git.defalsify.org/vise.git/logging" + dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" + "git.grassecon.net/urdt/ussd/models" +) + +var ( + logg = logging.NewVanilla().WithDomain("term-testutiL") +) + +const ( + AliceChecksum = "0xeae046BF396e91f5A8D74f863dC57c107c8a4a70" + BobChecksum = "0xB3117202371853e24B725d4169D87616A7dDb127" + AliceSession = "5553425" +) + +type MockApi struct { + TransactionsContent []dataserviceapi.Last10TxResponse + VouchersContent []dataserviceapi.TokenHoldings + VoucherDataContent *models.VoucherDataResult +} + +func(m MockApi) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResult, error) { + return nil, nil +} + +func(m MockApi) CreateAccount(ctx context.Context) (*models.AccountResult, error) { + return nil, nil +} + +func(m MockApi) TrackAccountStatus(ctx context.Context, publicKey string) (*models.TrackStatusResult, error) { + return nil, nil +} + +func(m MockApi) FetchVouchers(ctx context.Context, publicKey string) ([]dataserviceapi.TokenHoldings, error) { + logg.DebugCtxf(ctx, "mockapi fetchvouchers", "key", publicKey) + return m.VouchersContent, nil +} + +func(m MockApi) FetchTransactions(ctx context.Context, publicKey string) ([]dataserviceapi.Last10TxResponse, error) { + logg.DebugCtxf(ctx, "mockapi fetchtransactions", "key", publicKey) + return m.TransactionsContent, nil +} + +func(m MockApi) VoucherData(ctx context.Context, address string) (*models.VoucherDataResult, error) { + return m.VoucherDataContent, nil +}