mirror of
https://github.com/GMWalletApp/epusdt.git
synced 2026-07-07 10:16:15 +00:00
feat(rate): support multi-currency forced rate list
This commit is contained in:
+20
-23
@@ -8,7 +8,6 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -17,12 +16,6 @@ import (
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// parseFloat is the minimal helper used by the settings bridge; kept
|
||||
// here so settings_bridge.go stays import-free.
|
||||
func parseFloat(s string) (float64, error) {
|
||||
return strconv.ParseFloat(strings.TrimSpace(s), 64)
|
||||
}
|
||||
|
||||
var (
|
||||
HTTPAccessLog bool
|
||||
SQLDebug bool
|
||||
@@ -353,21 +346,25 @@ func GetRateForCoin(coin string, base string) float64 {
|
||||
if coin == base {
|
||||
return 1
|
||||
}
|
||||
if coin == "usdt" {
|
||||
switch base {
|
||||
case "usd":
|
||||
return 1
|
||||
case "cny":
|
||||
usdtRate := GetUsdtRate()
|
||||
if usdtRate > 0 {
|
||||
return 1 / usdtRate
|
||||
}
|
||||
return 0
|
||||
}
|
||||
if forcedRate := getForcedRateForCoin(coin, base); forcedRate > 0 {
|
||||
return forcedRate
|
||||
}
|
||||
if coin == "usdt" && base == "usd" {
|
||||
return 1
|
||||
}
|
||||
return getRateForCoinFromAPI(coin, base)
|
||||
}
|
||||
|
||||
func getForcedRateForCoin(coin string, base string) float64 {
|
||||
raw := strings.TrimSpace(settingsForcedRateList())
|
||||
if raw != "" {
|
||||
if rate := gjson.Get(raw, base+"."+coin).Float(); rate > 0 {
|
||||
return rate
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func getRateForCoinFromAPI(coin string, base string) float64 {
|
||||
baseURL := GetRateApiUrl()
|
||||
if baseURL == "" {
|
||||
@@ -401,17 +398,17 @@ func getRateForCoinFromAPI(coin string, base string) float64 {
|
||||
}
|
||||
|
||||
func GetUsdtRate() float64 {
|
||||
// Only the admin setting can force the USDT/CNY rate. When the
|
||||
// setting is unset, zero, or negative, fall back to the rate API.
|
||||
if forced := settingsForcedUsdtRate(); forced > 0 {
|
||||
return forced
|
||||
// rate.forced_rate_list stores token amount per one base currency unit.
|
||||
// GetUsdtRate returns the inverse display value: CNY per USDT.
|
||||
if forcedRate := getForcedRateForCoin("usdt", "cny"); forcedRate > 0 {
|
||||
return 1 / forcedRate
|
||||
}
|
||||
|
||||
apiRate := getRateForCoinFromAPI("usdt", "cny")
|
||||
if apiRate > 0 {
|
||||
return 1 / apiRate
|
||||
}
|
||||
log.Printf("usdt/cny rate unavailable: rate.forced_usdt_rate <= 0 and rate api returned no data")
|
||||
log.Printf("usdt/cny rate unavailable: rate.forced_rate_list has no positive cny.usdt entry and rate api returned no data")
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
@@ -231,7 +231,7 @@ func TestCopyMissingStaticFilesCopiesAssetsWithoutOverwriting(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUsdtRatePrefersPositiveAdminOverride(t *testing.T) {
|
||||
func TestGetUsdtRatePrefersForcedRateList(t *testing.T) {
|
||||
viper.Reset()
|
||||
t.Cleanup(viper.Reset)
|
||||
t.Setenv("API_RATE_URL", "")
|
||||
@@ -249,16 +249,19 @@ func TestGetUsdtRatePrefersPositiveAdminOverride(t *testing.T) {
|
||||
})
|
||||
|
||||
installSettingsGetter(t, map[string]string{
|
||||
"rate.forced_usdt_rate": "7.25",
|
||||
"rate.forced_rate_list": `{"cny":{"usdt":0.13793103448275862},"usd":{"trx":123.45}}`,
|
||||
"rate.api_url": "https://rate.example.test",
|
||||
})
|
||||
|
||||
got := GetUsdtRate()
|
||||
if got != 7.25 {
|
||||
if math.Abs(got-7.25) > 1e-9 {
|
||||
t.Fatalf("GetUsdtRate() = %v, want 7.25", got)
|
||||
}
|
||||
if rate := GetRateForCoin("trx", "usd"); rate != 123.45 {
|
||||
t.Fatalf("GetRateForCoin(trx, usd) = %v, want 123.45", rate)
|
||||
}
|
||||
if apiCalled {
|
||||
t.Fatalf("rate API should not be called when rate.forced_usdt_rate > 0")
|
||||
t.Fatalf("rate API should not be called when rate.forced_rate_list has positive rate")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,7 +284,7 @@ func TestGetUsdtRateUsesAPIWhenAdminOverrideIsNotPositive(t *testing.T) {
|
||||
})
|
||||
|
||||
installSettingsGetter(t, map[string]string{
|
||||
"rate.forced_usdt_rate": "-1",
|
||||
"rate.forced_rate_list": `{"cny":{"usdt":0}}`,
|
||||
"rate.api_url": "https://rate.example.test",
|
||||
})
|
||||
|
||||
@@ -297,6 +300,35 @@ func TestGetUsdtRateUsesAPIWhenAdminOverrideIsNotPositive(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRateForCoinUsesAPIWhenForcedPairMissing(t *testing.T) {
|
||||
viper.Reset()
|
||||
t.Cleanup(viper.Reset)
|
||||
t.Setenv("API_RATE_URL", "")
|
||||
|
||||
installMockHTTPClient(t, func(r *http.Request) (*http.Response, error) {
|
||||
if r.URL.Path != "/cny.json" {
|
||||
t.Fatalf("rate api path = %s, want /cny.json", r.URL.Path)
|
||||
}
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Status: "200 OK",
|
||||
Header: http.Header{"Content-Type": []string{"application/json"}},
|
||||
Body: io.NopCloser(strings.NewReader(`{"cny":{"usdt":0.14635}}`)),
|
||||
Request: r,
|
||||
}, nil
|
||||
})
|
||||
|
||||
installSettingsGetter(t, map[string]string{
|
||||
"rate.forced_rate_list": `{"usd":{"trx":123.45}}`,
|
||||
"rate.api_url": "https://rate.example.test",
|
||||
})
|
||||
|
||||
rate := GetRateForCoin("usdt", "cny")
|
||||
if math.Abs(rate-0.14635) > 1e-9 {
|
||||
t.Fatalf("GetRateForCoin(usdt, cny) = %v, want 0.14635", rate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUsdtRateReturnsZeroWhenAPIUnavailableWithoutAdminOverride(t *testing.T) {
|
||||
viper.Reset()
|
||||
t.Cleanup(viper.Reset)
|
||||
@@ -313,7 +345,7 @@ func TestGetUsdtRateReturnsZeroWhenAPIUnavailableWithoutAdminOverride(t *testing
|
||||
})
|
||||
|
||||
installSettingsGetter(t, map[string]string{
|
||||
"rate.forced_usdt_rate": "0",
|
||||
"rate.forced_rate_list": `{"cny":{"usdt":0}}`,
|
||||
"rate.api_url": "https://rate.example.test",
|
||||
})
|
||||
|
||||
@@ -343,7 +375,7 @@ func TestGetRateForCoinCallsRateAPIOnceForUsdtCnyFailure(t *testing.T) {
|
||||
})
|
||||
|
||||
installSettingsGetter(t, map[string]string{
|
||||
"rate.forced_usdt_rate": "0",
|
||||
"rate.forced_rate_list": `{"cny":{"usdt":0}}`,
|
||||
"rate.api_url": "https://rate.example.test",
|
||||
})
|
||||
|
||||
|
||||
@@ -18,17 +18,9 @@ func settingsRateApiUrl() string {
|
||||
return SettingsGetString("rate.api_url")
|
||||
}
|
||||
|
||||
func settingsForcedUsdtRate() float64 {
|
||||
func settingsForcedRateList() string {
|
||||
if SettingsGetString == nil {
|
||||
return 0
|
||||
return ""
|
||||
}
|
||||
raw := SettingsGetString("rate.forced_usdt_rate")
|
||||
if raw == "" {
|
||||
return 0
|
||||
}
|
||||
f, err := parseFloat(raw)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return f
|
||||
return SettingsGetString("rate.forced_rate_list")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user