EVM, ARB, ETH gas top up policies + tron config change
This commit is contained in:
@@ -39,10 +39,10 @@ chains:
|
|||||||
native_token: TRX
|
native_token: TRX
|
||||||
rpc_url_env: CHAIN_GATEWAY_RPC_URL
|
rpc_url_env: CHAIN_GATEWAY_RPC_URL
|
||||||
gas_topup_policy:
|
gas_topup_policy:
|
||||||
buffer_percent: 0.15
|
buffer_percent: 0.10
|
||||||
min_native_balance_trx: 20
|
min_native_balance_trx: 10
|
||||||
rounding_unit_trx: 1
|
rounding_unit_trx: 1
|
||||||
max_topup_trx: 500
|
max_topup_trx: 100
|
||||||
tokens:
|
tokens:
|
||||||
- symbol: USDT
|
- symbol: USDT
|
||||||
contract: "0xa614f803b6fd780986a42c78ec9c7f77e6ded13c"
|
contract: "0xa614f803b6fd780986a42c78ec9c7f77e6ded13c"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/commands/wallet"
|
"github.com/tech/sendico/gateway/chain/internal/service/gateway/commands/wallet"
|
||||||
|
"github.com/tech/sendico/gateway/chain/internal/service/gateway/driver/evm"
|
||||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/driver/tron"
|
"github.com/tech/sendico/gateway/chain/internal/service/gateway/driver/tron"
|
||||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
|
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
|
||||||
"github.com/tech/sendico/gateway/chain/storage/model"
|
"github.com/tech/sendico/gateway/chain/storage/model"
|
||||||
@@ -171,6 +172,14 @@ func computeGasTopUp(ctx context.Context, deps Deps, walletRef string, estimated
|
|||||||
return topUp, decision.CapHit, &decision, nativeBalance, walletModel, nil
|
return topUp, decision.CapHit, &decision, nativeBalance, walletModel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if networkCfg.GasTopUpPolicy != nil {
|
||||||
|
topUp, capHit, err := evm.ComputeGasTopUp(networkCfg, walletModel, estimatedFee, nativeBalance)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
return topUp, capHit, nil, nativeBalance, walletModel, nil
|
||||||
|
}
|
||||||
|
|
||||||
topUp, err := defaultGasTopUp(estimatedFee, nativeBalance)
|
topUp, err := defaultGasTopUp(estimatedFee, nativeBalance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, nil, nil, nil, err
|
return nil, false, nil, nil, nil, err
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package evm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
|
||||||
|
"github.com/tech/sendico/gateway/chain/storage/model"
|
||||||
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
|
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var evmBaseUnitFactor = decimal.NewFromInt(1_000_000_000_000_000_000)
|
||||||
|
|
||||||
|
// ComputeGasTopUp applies the network policy to decide an EVM native-token top-up amount.
|
||||||
|
func ComputeGasTopUp(network shared.Network, wallet *model.ManagedWallet, estimatedFee *moneyv1.Money, currentBalance *moneyv1.Money) (*moneyv1.Money, bool, error) {
|
||||||
|
if wallet == nil {
|
||||||
|
return nil, false, merrors.InvalidArgument("wallet is required")
|
||||||
|
}
|
||||||
|
if estimatedFee == nil || strings.TrimSpace(estimatedFee.GetAmount()) == "" || strings.TrimSpace(estimatedFee.GetCurrency()) == "" {
|
||||||
|
return nil, false, merrors.InvalidArgument("estimated fee is required")
|
||||||
|
}
|
||||||
|
if currentBalance == nil || strings.TrimSpace(currentBalance.GetAmount()) == "" || strings.TrimSpace(currentBalance.GetCurrency()) == "" {
|
||||||
|
return nil, false, merrors.InvalidArgument("current native balance is required")
|
||||||
|
}
|
||||||
|
if network.GasTopUpPolicy == nil {
|
||||||
|
return nil, false, merrors.InvalidArgument("gas top-up policy is not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
nativeCurrency := strings.TrimSpace(network.NativeToken)
|
||||||
|
if nativeCurrency == "" {
|
||||||
|
nativeCurrency = strings.ToUpper(strings.TrimSpace(network.Name))
|
||||||
|
}
|
||||||
|
if !strings.EqualFold(nativeCurrency, estimatedFee.GetCurrency()) {
|
||||||
|
return nil, false, merrors.InvalidArgument(fmt.Sprintf("estimated fee currency mismatch (expected %s)", nativeCurrency))
|
||||||
|
}
|
||||||
|
if !strings.EqualFold(nativeCurrency, currentBalance.GetCurrency()) {
|
||||||
|
return nil, false, merrors.InvalidArgument(fmt.Sprintf("native balance currency mismatch (expected %s)", nativeCurrency))
|
||||||
|
}
|
||||||
|
|
||||||
|
estimatedNative, err := evmToNative(estimatedFee)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
currentNative, err := evmToNative(currentBalance)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
isContract := strings.TrimSpace(wallet.ContractAddress) != ""
|
||||||
|
rule, ok := network.GasTopUpPolicy.Rule(isContract)
|
||||||
|
if !ok {
|
||||||
|
return nil, false, merrors.InvalidArgument("gas top-up policy is not configured")
|
||||||
|
}
|
||||||
|
if rule.RoundingUnit.LessThanOrEqual(decimal.Zero) {
|
||||||
|
return nil, false, merrors.InvalidArgument("gas top-up rounding unit must be > 0")
|
||||||
|
}
|
||||||
|
if rule.MaxTopUp.LessThanOrEqual(decimal.Zero) {
|
||||||
|
return nil, false, merrors.InvalidArgument("gas top-up max top-up must be > 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
required := estimatedNative.Sub(currentNative)
|
||||||
|
if required.IsNegative() {
|
||||||
|
required = decimal.Zero
|
||||||
|
}
|
||||||
|
bufferedRequired := required.Mul(decimal.NewFromInt(1).Add(rule.BufferPercent))
|
||||||
|
|
||||||
|
minBalanceTopUp := rule.MinNativeBalance.Sub(currentNative)
|
||||||
|
if minBalanceTopUp.IsNegative() {
|
||||||
|
minBalanceTopUp = decimal.Zero
|
||||||
|
}
|
||||||
|
|
||||||
|
rawTopUp := bufferedRequired
|
||||||
|
if minBalanceTopUp.GreaterThan(rawTopUp) {
|
||||||
|
rawTopUp = minBalanceTopUp
|
||||||
|
}
|
||||||
|
|
||||||
|
roundedTopUp := decimal.Zero
|
||||||
|
if rawTopUp.IsPositive() {
|
||||||
|
roundedTopUp = rawTopUp.Div(rule.RoundingUnit).Ceil().Mul(rule.RoundingUnit)
|
||||||
|
}
|
||||||
|
|
||||||
|
topUp := roundedTopUp
|
||||||
|
capHit := false
|
||||||
|
if topUp.GreaterThan(rule.MaxTopUp) {
|
||||||
|
topUp = rule.MaxTopUp
|
||||||
|
capHit = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !topUp.IsPositive() {
|
||||||
|
return nil, capHit, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
baseUnits := topUp.Mul(evmBaseUnitFactor).Ceil().Truncate(0)
|
||||||
|
return &moneyv1.Money{
|
||||||
|
Currency: strings.ToUpper(nativeCurrency),
|
||||||
|
Amount: baseUnits.StringFixed(0),
|
||||||
|
}, capHit, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func evmToNative(amount *moneyv1.Money) (decimal.Decimal, error) {
|
||||||
|
value, err := decimal.NewFromString(strings.TrimSpace(amount.GetAmount()))
|
||||||
|
if err != nil {
|
||||||
|
return decimal.Zero, err
|
||||||
|
}
|
||||||
|
return value.Div(evmBaseUnitFactor), nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
package evm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
|
||||||
|
"github.com/tech/sendico/gateway/chain/storage/model"
|
||||||
|
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestComputeGasTopUp_BalanceSufficient(t *testing.T) {
|
||||||
|
network := ethNetwork(defaultPolicy())
|
||||||
|
wallet := &model.ManagedWallet{}
|
||||||
|
|
||||||
|
topUp, capHit, err := ComputeGasTopUp(network, wallet, ethMoney("5"), ethMoney("30"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Nil(t, topUp)
|
||||||
|
require.False(t, capHit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeGasTopUp_BufferedRequired(t *testing.T) {
|
||||||
|
network := ethNetwork(defaultPolicy())
|
||||||
|
wallet := &model.ManagedWallet{}
|
||||||
|
|
||||||
|
topUp, capHit, err := ComputeGasTopUp(network, wallet, ethMoney("50"), ethMoney("10"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, topUp)
|
||||||
|
require.False(t, capHit)
|
||||||
|
require.Equal(t, "46000000000000000000", topUp.GetAmount())
|
||||||
|
require.Equal(t, "ETH", topUp.GetCurrency())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeGasTopUp_MinBalanceBinding(t *testing.T) {
|
||||||
|
network := ethNetwork(defaultPolicy())
|
||||||
|
wallet := &model.ManagedWallet{}
|
||||||
|
|
||||||
|
topUp, capHit, err := ComputeGasTopUp(network, wallet, ethMoney("5"), ethMoney("1"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, topUp)
|
||||||
|
require.False(t, capHit)
|
||||||
|
require.Equal(t, "19000000000000000000", topUp.GetAmount())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeGasTopUp_RoundsUp(t *testing.T) {
|
||||||
|
policy := shared.GasTopUpPolicy{
|
||||||
|
Default: shared.GasTopUpRule{
|
||||||
|
BufferPercent: decimal.NewFromFloat(0),
|
||||||
|
MinNativeBalance: decimal.NewFromFloat(0),
|
||||||
|
RoundingUnit: decimal.NewFromFloat(1),
|
||||||
|
MaxTopUp: decimal.NewFromFloat(100),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
network := ethNetwork(&policy)
|
||||||
|
wallet := &model.ManagedWallet{}
|
||||||
|
|
||||||
|
topUp, capHit, err := ComputeGasTopUp(network, wallet, ethMoney("1.1"), ethMoney("0"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, topUp)
|
||||||
|
require.False(t, capHit)
|
||||||
|
require.Equal(t, "2000000000000000000", topUp.GetAmount())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeGasTopUp_CapHit(t *testing.T) {
|
||||||
|
policy := shared.GasTopUpPolicy{
|
||||||
|
Default: shared.GasTopUpRule{
|
||||||
|
BufferPercent: decimal.NewFromFloat(0),
|
||||||
|
MinNativeBalance: decimal.NewFromFloat(0),
|
||||||
|
RoundingUnit: decimal.NewFromFloat(1),
|
||||||
|
MaxTopUp: decimal.NewFromFloat(10),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
network := ethNetwork(&policy)
|
||||||
|
wallet := &model.ManagedWallet{}
|
||||||
|
|
||||||
|
topUp, capHit, err := ComputeGasTopUp(network, wallet, ethMoney("100"), ethMoney("0"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, topUp)
|
||||||
|
require.True(t, capHit)
|
||||||
|
require.Equal(t, "10000000000000000000", topUp.GetAmount())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeGasTopUp_MinBalanceWhenRequiredZero(t *testing.T) {
|
||||||
|
network := ethNetwork(defaultPolicy())
|
||||||
|
wallet := &model.ManagedWallet{}
|
||||||
|
|
||||||
|
topUp, capHit, err := ComputeGasTopUp(network, wallet, ethMoney("0"), ethMoney("5"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, topUp)
|
||||||
|
require.False(t, capHit)
|
||||||
|
require.Equal(t, "15000000000000000000", topUp.GetAmount())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeGasTopUp_ContractPolicyOverride(t *testing.T) {
|
||||||
|
policy := shared.GasTopUpPolicy{
|
||||||
|
Default: shared.GasTopUpRule{
|
||||||
|
BufferPercent: decimal.NewFromFloat(0.1),
|
||||||
|
MinNativeBalance: decimal.NewFromFloat(10),
|
||||||
|
RoundingUnit: decimal.NewFromFloat(1),
|
||||||
|
MaxTopUp: decimal.NewFromFloat(100),
|
||||||
|
},
|
||||||
|
Contract: &shared.GasTopUpRule{
|
||||||
|
BufferPercent: decimal.NewFromFloat(0.5),
|
||||||
|
MinNativeBalance: decimal.NewFromFloat(5),
|
||||||
|
RoundingUnit: decimal.NewFromFloat(1),
|
||||||
|
MaxTopUp: decimal.NewFromFloat(100),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
network := ethNetwork(&policy)
|
||||||
|
wallet := &model.ManagedWallet{ContractAddress: "0xcontract"}
|
||||||
|
|
||||||
|
topUp, capHit, err := ComputeGasTopUp(network, wallet, ethMoney("10"), ethMoney("0"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, topUp)
|
||||||
|
require.False(t, capHit)
|
||||||
|
require.Equal(t, "15000000000000000000", topUp.GetAmount())
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultPolicy() *shared.GasTopUpPolicy {
|
||||||
|
return &shared.GasTopUpPolicy{
|
||||||
|
Default: shared.GasTopUpRule{
|
||||||
|
BufferPercent: decimal.NewFromFloat(0.15),
|
||||||
|
MinNativeBalance: decimal.NewFromFloat(20),
|
||||||
|
RoundingUnit: decimal.NewFromFloat(1),
|
||||||
|
MaxTopUp: decimal.NewFromFloat(500),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ethNetwork(policy *shared.GasTopUpPolicy) shared.Network {
|
||||||
|
return shared.Network{
|
||||||
|
Name: "ethereum_mainnet",
|
||||||
|
NativeToken: "ETH",
|
||||||
|
GasTopUpPolicy: policy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ethMoney(eth string) *moneyv1.Money {
|
||||||
|
value, _ := decimal.NewFromString(eth)
|
||||||
|
baseUnits := value.Mul(evmBaseUnitFactor).Truncate(0)
|
||||||
|
return &moneyv1.Money{
|
||||||
|
Currency: "ETH",
|
||||||
|
Amount: baseUnits.StringFixed(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user