status-go/services/wallet/requests/router_input_params.go

205 lines
8.5 KiB
Go

package requests
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/status-im/status-go/errors"
walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/router/fees"
"github.com/status-im/status-go/services/wallet/router/sendtype"
"github.com/status-im/status-go/services/wallet/token"
)
var (
ErrENSRegisterRequiresUsernameAndPubKey = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-001"), Details: "username and public key are required for ENSRegister"}
ErrENSRegisterTestnetSTTOnly = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-002"), Details: "only STT is supported for ENSRegister on testnet"}
ErrENSRegisterMainnetSNTOnly = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-003"), Details: "only SNT is supported for ENSRegister on mainnet"}
ErrENSReleaseRequiresUsername = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-004"), Details: "username is required for ENSRelease"}
ErrENSSetPubKeyRequiresUsernameAndPubKey = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-005"), Details: "username and public key are required for ENSSetPubKey"}
ErrStickersBuyRequiresPackID = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-006"), Details: "packID is required for StickersBuy"}
ErrSwapRequiresToTokenID = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-007"), Details: "toTokenID is required for Swap"}
ErrSwapTokenIDMustBeDifferent = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-008"), Details: "tokenID and toTokenID must be different"}
ErrSwapAmountInAmountOutMustBeExclusive = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-009"), Details: "only one of amountIn or amountOut can be set"}
ErrSwapAmountInMustBePositive = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-010"), Details: "amountIn must be positive"}
ErrSwapAmountOutMustBePositive = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-011"), Details: "amountOut must be positive"}
ErrLockedAmountNotSupportedForNetwork = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-012"), Details: "locked amount is not supported for the selected network"}
ErrLockedAmountNotNegative = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-013"), Details: "locked amount must not be negative"}
ErrLockedAmountExceedsTotalSendAmount = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-014"), Details: "locked amount exceeds the total amount to send"}
ErrLockedAmountLessThanSendAmountAllNetworks = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-015"), Details: "locked amount is less than the total amount to send, but all networks are locked"}
ErrDisabledChainFoundAmongLockedNetworks = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-016"), Details: "disabled chain found among locked networks"}
ErrENSSetPubKeyInvalidUsername = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-017"), Details: "a valid username, ending in '.eth', is required for ENSSetPubKey"}
ErrLockedAmountExcludesAllSupported = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-018"), Details: "all supported chains are excluded, routing impossible"}
ErrCannotCheckLockedAmounts = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-019"), Details: "cannot check locked amounts"}
)
type RouteInputParams struct {
Uuid string `json:"uuid"`
SendType sendtype.SendType `json:"sendType" validate:"required"`
AddrFrom common.Address `json:"addrFrom" validate:"required"`
AddrTo common.Address `json:"addrTo" validate:"required"`
AmountIn *hexutil.Big `json:"amountIn" validate:"required"`
AmountOut *hexutil.Big `json:"amountOut"`
TokenID string `json:"tokenID" validate:"required"`
TokenIDIsOwnerToken bool `json:"tokenIDIsOwnerToken"`
ToTokenID string `json:"toTokenID"`
DisabledFromChainIDs []uint64 `json:"disabledFromChainIDs"`
DisabledToChainIDs []uint64 `json:"disabledToChainIDs"`
GasFeeMode fees.GasFeeMode `json:"gasFeeMode" validate:"required"`
FromLockedAmount map[uint64]*hexutil.Big `json:"fromLockedAmount"`
TestnetMode bool
// For send types like EnsRegister, EnsRelease, EnsSetPubKey, StickersBuy
Username string `json:"username"`
PublicKey string `json:"publicKey"`
PackID *hexutil.Big `json:"packID"`
// TODO: Remove two fields below once we implement a better solution for tests
// Currently used for tests only
TestsMode bool
TestParams *RouterTestParams
}
type RouterTestParams struct {
TokenFrom *token.Token
TokenPrices map[string]float64
EstimationMap map[string]Estimation // [processor-name, estimation]
BonderFeeMap map[string]*big.Int // [token-symbol, bonder-fee]
SuggestedFees *fees.SuggestedFees
BaseFee *big.Int
BalanceMap map[string]*big.Int // [token-symbol, balance]
ApprovalGasEstimation uint64
ApprovalL1Fee uint64
}
type Estimation struct {
Value uint64
Err error
}
func (i *RouteInputParams) Validate() error {
if i.SendType == sendtype.ENSRegister {
if i.Username == "" || i.PublicKey == "" {
return ErrENSRegisterRequiresUsernameAndPubKey
}
if i.TestnetMode {
if i.TokenID != walletCommon.SttSymbol {
return ErrENSRegisterTestnetSTTOnly
}
} else {
if i.TokenID != walletCommon.SntSymbol {
return ErrENSRegisterMainnetSNTOnly
}
}
return nil
}
if i.SendType == sendtype.ENSRelease {
if i.Username == "" {
return ErrENSReleaseRequiresUsername
}
}
if i.SendType == sendtype.ENSSetPubKey {
if i.Username == "" || i.PublicKey == "" {
return ErrENSSetPubKeyRequiresUsernameAndPubKey
}
if walletCommon.ValidateENSUsername(i.Username) != nil {
return ErrENSSetPubKeyInvalidUsername
}
}
if i.SendType == sendtype.StickersBuy {
if i.PackID == nil {
return ErrStickersBuyRequiresPackID
}
}
if i.SendType == sendtype.Swap {
if i.ToTokenID == "" {
return ErrSwapRequiresToTokenID
}
if i.TokenID == i.ToTokenID {
return ErrSwapTokenIDMustBeDifferent
}
if i.AmountIn != nil &&
i.AmountOut != nil &&
i.AmountIn.ToInt().Cmp(walletCommon.ZeroBigIntValue()) > 0 &&
i.AmountOut.ToInt().Cmp(walletCommon.ZeroBigIntValue()) > 0 {
return ErrSwapAmountInAmountOutMustBeExclusive
}
if i.AmountIn != nil && i.AmountIn.ToInt().Sign() < 0 {
return ErrSwapAmountInMustBePositive
}
if i.AmountOut != nil && i.AmountOut.ToInt().Sign() < 0 {
return ErrSwapAmountOutMustBePositive
}
}
return i.validateFromLockedAmount()
}
func (i *RouteInputParams) validateFromLockedAmount() error {
if i.FromLockedAmount == nil || len(i.FromLockedAmount) == 0 {
return nil
}
var suppNetworks map[uint64]bool
if i.TestnetMode {
suppNetworks = walletCommon.CopyMapGeneric(walletCommon.SupportedTestNetworks, nil).(map[uint64]bool)
} else {
suppNetworks = walletCommon.CopyMapGeneric(walletCommon.SupportedNetworks, nil).(map[uint64]bool)
}
if suppNetworks == nil {
return ErrCannotCheckLockedAmounts
}
totalLockedAmount := big.NewInt(0)
excludedChainCount := 0
for chainID, amount := range i.FromLockedAmount {
if walletCommon.ArrayContainsElement(chainID, i.DisabledFromChainIDs) {
return ErrDisabledChainFoundAmongLockedNetworks
}
if i.TestnetMode {
if !walletCommon.SupportedTestNetworks[chainID] {
return ErrLockedAmountNotSupportedForNetwork
}
} else {
if !walletCommon.SupportedNetworks[chainID] {
return ErrLockedAmountNotSupportedForNetwork
}
}
if amount == nil || amount.ToInt().Sign() < 0 {
return ErrLockedAmountNotNegative
}
if !(amount.ToInt().Sign() > 0) {
excludedChainCount++
}
delete(suppNetworks, chainID)
totalLockedAmount = new(big.Int).Add(totalLockedAmount, amount.ToInt())
}
if (!i.TestnetMode && excludedChainCount == len(walletCommon.SupportedNetworks)) ||
(i.TestnetMode && excludedChainCount == len(walletCommon.SupportedTestNetworks)) {
return ErrLockedAmountExcludesAllSupported
}
if totalLockedAmount.Cmp(i.AmountIn.ToInt()) > 0 {
return ErrLockedAmountExceedsTotalSendAmount
}
if totalLockedAmount.Cmp(i.AmountIn.ToInt()) < 0 && len(suppNetworks) == 0 {
return ErrLockedAmountLessThanSendAmountAllNetworks
}
return nil
}