Skip to content

Task/sean/str 204 #237

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func NewServices(config APIConfig, repos repository.Repositories) service.Servic
device := service.NewDevice(deviceRepos, fingerprint)

auth := service.NewAuth(repos, verification, device)
cost := service.NewCost(config.Redis)
cost := service.NewCost(config.Redis, repos)
executor := service.NewExecutor()
geofencing := service.NewGeofencing(config.Redis)

Expand Down
12 changes: 12 additions & 0 deletions pkg/internal/common/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io"
"math"
"strconv"
"strings"

libcommon "github.com/String-xyz/go-lib/v2/common"
"github.com/ethereum/go-ethereum/accounts"
Expand Down Expand Up @@ -70,3 +71,14 @@ func SliceContains(elems []string, v string) bool {
}
return false
}

func StringContainsAny(target string, substrs []string) bool {
// convert target to lowercase
noSpaceLowerCase := strings.ReplaceAll(strings.ToLower(target), " ", "")
for _, substr := range substrs {
if strings.Contains(noSpaceLowerCase, strings.ReplaceAll(strings.ToLower(substr), " ", "")) {
return true
}
}
return false
}
8 changes: 4 additions & 4 deletions pkg/internal/unit21/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,12 @@ func mockTransactionRows(mock sqlmock.Sqlmock, transaction model.Transaction, us
AddRow(transaction.DestinationTxLegId, time.Now(), "1", transaction.TransactionAmount, assetId2, userId, instrumentId2)
mock.ExpectQuery("SELECT * FROM tx_leg WHERE id = $1 AND deleted_at IS NULL").WithArgs(transaction.DestinationTxLegId).WillReturnRows(mockedTxLegRow2)

mockedAssetRow1 := sqlmock.NewRows([]string{"id", "name", "description", "decimals", "is_crypto", "value_oracle"}).
AddRow(assetId1, "USD", "fiat USD", 6, false, "self")
mockedAssetRow1 := sqlmock.NewRows([]string{"id", "name", "description", "decimals", "is_crypto", "network_id", "value_oracle"}).
AddRow(assetId1, "USD", "fiat USD", 6, false, transaction.NetworkId, "self")
mock.ExpectQuery("SELECT * FROM asset WHERE id = $1 AND deleted_at IS NULL").WithArgs(assetId1).WillReturnRows(mockedAssetRow1)

mockedAssetRow2 := sqlmock.NewRows([]string{"id", "name", "description", "decimals", "is_crypto", "value_oracle"}).
AddRow(assetId2, "Noose The Goose", "Noose the Goose NFT", 0, true, "joepegs.com")
mockedAssetRow2 := sqlmock.NewRows([]string{"id", "name", "description", "decimals", "is_crypto", "network_id", "value_oracle"}).
AddRow(assetId2, "Noose The Goose", "Noose the Goose NFT", 0, true, transaction.NetworkId, "joepegs.com")
mock.ExpectQuery("SELECT * FROM asset WHERE id = $1 AND deleted_at IS NULL").WithArgs(assetId2).WillReturnRows(mockedAssetRow2)

mockedDeviceRow := sqlmock.NewRows([]string{"id", "type", "description", "fingerprint", "ip_addresses", "user_id"}).
Expand Down
2 changes: 2 additions & 0 deletions pkg/model/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,10 @@ type Asset struct {
Description string `json:"description" db:"description"`
Decimals uint64 `json:"decimals" db:"decimals"`
IsCrypto bool `json:"isCrypto" db:"is_crypto"`
NetworkId string `json:"networkId" db:"network_id"`
ValueOracle sql.NullString `json:"valueOracle" db:"value_oracle"`
ValueOracle2 sql.NullString `json:"valueOracle2" db:"value_oracle_2"`
Address sql.NullString `json:"address" db:"address"`
}

type UserToPlatform struct {
Expand Down
22 changes: 12 additions & 10 deletions pkg/repository/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Asset interface {
Create(ctx context.Context, m model.Asset) (model.Asset, error)
GetById(ctx context.Context, id string) (model.Asset, error)
GetByName(ctx context.Context, name string) (model.Asset, error)
GetByKey(ctx context.Context, networkId string, address string) (model.Asset, error)
Update(ctx context.Context, Id string, updates any) error
}

Expand All @@ -32,16 +33,8 @@ func (a asset[T]) Create(ctx context.Context, insert model.Asset) (model.Asset,
m := model.Asset{}

query, args, err := a.Named(`
WITH insert_asset AS (
INSERT INTO asset (name, description, decimals, is_crypto, value_oracle, value_oracle_2)
VALUES(:name, :description, :decimals, :is_crypto, :value_oracle, :value_oracle_2)
ON CONFLICT (name) DO NOTHING
RETURNING *
)
INSERT INTO asset_to_network (asset_id, network_id)
VALUES(insert_asset.id, :network_id)
ON CONFLICT (asset_id, network_id) DO NOTHING
`, insert)
INSERT INTO asset (name, description, decimals, is_crypto, network_id, value_oracle, value_oracle_2, address)
VALUES(:name, :description, :decimals, :is_crypto, :network_id, :value_oracle, :value_oracle_2, :address) RETURNING *`, insert)

if err != nil {
return m, libcommon.StringError(err)
Expand All @@ -64,3 +57,12 @@ func (a asset[T]) GetByName(ctx context.Context, name string) (model.Asset, erro
}
return m, nil
}

func (a asset[T]) GetByKey(ctx context.Context, networkId string, address string) (model.Asset, error) {
m := model.Asset{}
err := a.Store.GetContext(ctx, &m, fmt.Sprintf("SELECT * FROM %s WHERE network_id = $1 AND address = $2", a.Table), networkId, address)
if err != nil && err == sql.ErrNoRows {
return m, serror.NOT_FOUND
}
return m, nil
}
53 changes: 52 additions & 1 deletion pkg/service/cost.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package service

import (
"context"
"database/sql"
"fmt"
"math"
"math/big"
Expand All @@ -13,6 +15,7 @@ import (
"github.com/String-xyz/string-api/config"
"github.com/String-xyz/string-api/pkg/internal/common"
"github.com/String-xyz/string-api/pkg/model"
"github.com/String-xyz/string-api/pkg/repository"
"github.com/String-xyz/string-api/pkg/store"
"github.com/pkg/errors"
)
Expand Down Expand Up @@ -77,14 +80,16 @@ type CostCache struct {
type Cost interface {
EstimateTransaction(p EstimationParams, chain Chain) (estimate model.Estimate[float64], err error)
LookupUSD(quantity float64, coins ...string) (float64, error)
AddCoinToAssetTable(id string, networkId string, address string) error
}

type cost struct {
redis database.RedisStore // cached token and gas costs
repos repository.Repositories
subnetTokenProxies map[CoinKey]CoinKey
}

func NewCost(redis database.RedisStore) Cost {
func NewCost(redis database.RedisStore, repos repository.Repositories) Cost {
// Temporarily hard-coding this to reduce future cost-of-change with database
subnetTokenProxies := map[CoinKey]CoinKey{
// USDc DFK Subnet -> USDc Avalanche:
Expand All @@ -94,6 +99,7 @@ func NewCost(redis database.RedisStore) Cost {
}
return &cost{
redis: redis,
repos: repos,
subnetTokenProxies: subnetTokenProxies,
}
}
Expand All @@ -118,6 +124,44 @@ func GetCoingeckoPlatformMapping() (map[uint64]string, map[string]uint64, error)
return idToPlatform, platformToId, nil
}

func GetCoingeckoCoinData(id string) (CoingeckoCoin, error) {
var coin CoingeckoCoin
err := common.GetJsonGeneric("https://api.coingecko.com/api/v3/coins/"+id+"?localization=false&tickers=false&market_data=false&community_data=false&developer_data=false&sparkline=false", &coin)
if err != nil {
return coin, libcommon.StringError(err)
}
return coin, nil
}

func (c cost) AddCoinToAssetTable(id string, networkId string, address string) error {
coinData, err := GetCoingeckoCoinData(id)
if err != nil {
return libcommon.StringError(err)
}
_, err = c.repos.Asset.GetByKey(context.Background(), networkId, address)
if err != nil && err == serror.NOT_FOUND {
// add it to the asset table
_, err := c.repos.Asset.Create(context.Background(), model.Asset{
Name: coinData.Symbol, // Name in our database is the Symbol
Description: coinData.Name, // Description in our database is the Name, note: coingecko provides an actual description
Decimals: 18, // TODO: Get this from coinData - it's listed per network. Decimals only affects display.
IsCrypto: true,
ValueOracle: sql.NullString{String: id, Valid: true},
NetworkId: networkId,
Address: sql.NullString{String: address, Valid: true},
// TODO: Get second oracle data using data from first oracle
})
if err != nil {
return libcommon.StringError(err)
}
} else if err != nil {
return libcommon.StringError(err)
}
// Check if we are on a new chain

return nil
}

func GetCoingeckoCoinMapping() (map[string]string, error) {
_, platformToId, err := GetCoingeckoPlatformMapping()
if err != nil {
Expand Down Expand Up @@ -243,6 +287,13 @@ func (c cost) EstimateTransaction(p EstimationParams, chain Chain) (estimate mod
if !ok {
return estimate, errors.New("CoinGecko does not list token " + p.TokenAddrs[i])
}

// Check if the token is in our database and add it if it's not in there
err = c.AddCoinToAssetTable(tokenName, chain.UUID, p.TokenAddrs[i])
if err != nil {
return estimate, libcommon.StringError(err)
}

tokenCost, err := c.LookupUSD(costTokenEth, tokenName)
if err != nil {
return estimate, libcommon.StringError(err)
Expand Down
7 changes: 7 additions & 0 deletions pkg/service/cost_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package service

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
Expand All @@ -13,3 +14,9 @@ func TestGetTokenPrices(t *testing.T) {
assert.True(t, ok)
assert.Equal(t, "usd-coin", name)
}

func TestGetTokenData(t *testing.T) {
coin, err := GetCoingeckoCoinData("defi-kingdoms")
assert.NoError(t, err)
fmt.Printf("%+v", coin)
}
6 changes: 3 additions & 3 deletions pkg/service/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,7 @@ func (t transaction) testTransaction(executor Executor, request model.Transactio
tokenAddresses := []string{}
tokenAmounts := []big.Int{}
for _, action := range request.Actions {
if strings.ToLower(strings.ReplaceAll(action.CxFunc, " ", "")) == "approve(address,uint256)" {
if common.StringContainsAny(action.CxFunc, []string{"approve", "transfer"}) {
tokenAddresses = append(tokenAddresses, action.CxAddr)
// It should be safe at this point to w3.I without panic
tokenAmounts = append(tokenAmounts, *w3.I(action.CxParams[1]))
Expand All @@ -655,7 +655,7 @@ func (t transaction) testTransaction(executor Executor, request model.Transactio
if err != nil {
return res, eth, CallEstimate{}, libcommon.StringError(err)
}
cost := NewCost(t.redis)
cost := NewCost(t.redis, t.repos)
estimationParams := EstimationParams{
ChainId: chainId,
CostETH: estimateEVM.Value,
Expand Down Expand Up @@ -860,7 +860,7 @@ func (t transaction) tenderTransaction(ctx context.Context, p transactionProcess
_, finish := Span(ctx, "service.transaction.tenderTransaction", SpanTag{"platformId": p.platformId})
defer finish()

cost := NewCost(t.redis)
cost := NewCost(t.redis, t.repos)
trueWei := big.NewInt(0).Add(p.cumulativeValue, big.NewInt(int64(p.trueGas)))
trueEth := common.WeiToEther(trueWei)
trueUSD, err := cost.LookupUSD(trueEth, p.chain.CoingeckoName, p.chain.CoincapName)
Expand Down