From f0b4f5848201a964cfc03c84bed516bdf81c0a6f Mon Sep 17 00:00:00 2001 From: cherry <60910788+cherry-yl-sh@users.noreply.github.com> Date: Tue, 4 Jun 2024 20:59:17 +0800 Subject: [PATCH] gas tank (#23) gas Tank --- cmd/server/main.go | 2 + common/data_utils/data_util.go | 2 +- common/global_const/pay_type.go | 3 +- common/global_const/sponsor.go | 17 ++ common/model/api_key_info.go | 7 +- common/model/api_response.go | 5 +- common/model/secret_config.go | 1 + common/model/sponsor.go | 28 ++ common/model/strategy.go | 5 +- common/network/ethereum_adaptable_executor.go | 52 +++- .../ethereum_adaptable_executor_test.go | 14 +- .../paymaster_data_generate.go | 2 +- common/price_compoent/price_util.go | 10 + common/utils/util.go | 18 +- common/utils/util_test.go | 2 +- config/config_test.go | 2 +- config/secret_config.go | 5 + gas_executor/gas_validate.go | 2 +- rpc_server/api/v1/paymaster.go | 4 +- rpc_server/api/v1/sponsor.go | 136 +++++++++ rpc_server/middlewares/rate_limit.go | 6 +- rpc_server/routers/routers_map.go | 14 +- schedulor/listener_test.go | 137 +++++++++ schedulor/user_op_event_listener.go | 220 ++++++++++++++ .../dashboard_service/dashboard_service.go | 18 +- service/operator/operator_test.go | 2 +- service/operator/try_pay_user_op_execute.go | 106 +++++-- sponsor_manager/sponsor_balance_repository.go | 78 +++++ .../sponsor_changelog_repository.go | 71 +++++ sponsor_manager/sponsor_service.go | 279 ++++++++++++++++++ sponsor_manager/sponsor_test.go | 132 +++++++++ 31 files changed, 1298 insertions(+), 82 deletions(-) create mode 100644 common/global_const/sponsor.go create mode 100644 common/model/sponsor.go create mode 100644 rpc_server/api/v1/sponsor.go create mode 100644 schedulor/listener_test.go create mode 100644 schedulor/user_op_event_listener.go create mode 100644 sponsor_manager/sponsor_balance_repository.go create mode 100644 sponsor_manager/sponsor_changelog_repository.go create mode 100644 sponsor_manager/sponsor_service.go create mode 100644 sponsor_manager/sponsor_test.go diff --git a/cmd/server/main.go b/cmd/server/main.go index 28a5ffbc..97475641 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -5,6 +5,7 @@ import ( "AAStarCommunity/EthPaymaster_BackService/envirment" "AAStarCommunity/EthPaymaster_BackService/rpc_server/routers" "AAStarCommunity/EthPaymaster_BackService/service/dashboard_service" + "AAStarCommunity/EthPaymaster_BackService/sponsor_manager" "flag" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" @@ -69,6 +70,7 @@ func initEngine(strategyPath string, basicConfigPath string, secretPath string) logrus.SetLevel(logrus.InfoLevel) } dashboard_service.Init() + sponsor_manager.Init() logrus.Infof("Environment: %s", envirment.Environment.Name) logrus.Infof("Debugger: %v", envirment.Environment.Debugger) Engine = routers.SetRouters() diff --git a/common/data_utils/data_util.go b/common/data_utils/data_util.go index a3fdf318..0bebead0 100644 --- a/common/data_utils/data_util.go +++ b/common/data_utils/data_util.go @@ -32,7 +32,7 @@ func GetUserOpWithPaymasterAndDataForSimulate(op user_op.UserOpInput, strategy * op.CallGasLimit = global_const.DummyCallGasLimit } - paymasterData, err := executor.GetPaymasterData(&op, strategy, paymasterDataInput) + paymasterData, _, err := executor.GetPaymasterData(&op, strategy, paymasterDataInput) if err != nil { return nil, err } diff --git a/common/global_const/pay_type.go b/common/global_const/pay_type.go index 46228e12..02a17ecf 100644 --- a/common/global_const/pay_type.go +++ b/common/global_const/pay_type.go @@ -3,7 +3,8 @@ package global_const type PayType string const ( - PayTypeVerifying PayType = "PayTypeVerifying" + PayTypeProjectSponsor PayType = "PayTypeProjectSponsor" PayTypeERC20 PayType = "PayTypeERC20" PayTypeSuperVerifying PayType = "PayTypeSuperVerifying" + PayTypeUserSponsor PayType = "PayTypeUserSponsor" ) diff --git a/common/global_const/sponsor.go b/common/global_const/sponsor.go new file mode 100644 index 00000000..d632d893 --- /dev/null +++ b/common/global_const/sponsor.go @@ -0,0 +1,17 @@ +package global_const + +type UpdateType string + +const ( + UpdateTypeDeposit UpdateType = "deposit" + UpdateTypeLock UpdateType = "lock" + UpdateTypeWithdraw UpdateType = "withdraw" + UpdateTypeRelease UpdateType = "release" +) + +type BalanceType string + +const ( + BalanceTypeAvailableBalance BalanceType = "available_balance" + BalanceTypeLockBalance BalanceType = "lock_balance" +) diff --git a/common/model/api_key_info.go b/common/model/api_key_info.go index 42b287d8..a18a33b9 100644 --- a/common/model/api_key_info.go +++ b/common/model/api_key_info.go @@ -3,7 +3,8 @@ package model import "golang.org/x/time/rate" type ApiKeyModel struct { - Disable bool `gorm:"column:disable;type:bool" json:"disable"` - ApiKey string `gorm:"column:api_key;type:varchar(255)" json:"api_key"` - RateLimit rate.Limit `gorm:"column:rate_limit;type:int" json:"rate_limit"` + Disable bool `json:"disable"` + ApiKey string `json:"api_key"` + RateLimit rate.Limit `json:"rate_limit"` + UserId int64 `json:"user_id"` } diff --git a/common/model/api_response.go b/common/model/api_response.go index f60390f7..2fd4dff8 100644 --- a/common/model/api_response.go +++ b/common/model/api_response.go @@ -49,9 +49,8 @@ type UserOpEstimateGas struct { PaymasterPostOpGasLimit *big.Int `json:"paymasterPostOpGasLimit" binding:"required"` GasFees *[32]byte `json:"gasFees" binding:"required"` } -type PayReceipt struct { - TransactionHash string `json:"transaction_hash"` - Sponsor string `json:"sponsor"` +type PayResponse struct { + PayType global_const.PayType `json:"pay_type"` } type GetSupportEntryPointResponse struct { diff --git a/common/model/secret_config.go b/common/model/secret_config.go index 5f8a4f81..c105af47 100644 --- a/common/model/secret_config.go +++ b/common/model/secret_config.go @@ -19,6 +19,7 @@ type SecretConfig struct { RelayDBConfig DBConfig `json:"relay_db_config"` ApiKeyTableName string `json:"api_key_table_name"` StrategyConfigTableName string `json:"strategy_config_table_name"` + FreeSponsorWhitelist []string `json:"free_sponsor_whitelist"` } type NetWorkSecretConfig struct { diff --git a/common/model/sponsor.go b/common/model/sponsor.go new file mode 100644 index 00000000..1be53091 --- /dev/null +++ b/common/model/sponsor.go @@ -0,0 +1,28 @@ +package model + +import "math/big" + +type DepositSponsorRequest struct { + Source string + Amount *big.Float + TxHash string + + TxInfo map[string]string + PayUserId string + IsTestNet bool +} +type WithdrawSponsorRequest struct { + Amount *big.Float + + PayUserId string + IsTestNet bool + TxInfo map[string]string + TxHash string +} +type GetSponsorTransactionsRequest struct { +} +type GetSponsorMetaDataRequest struct { +} + +type Transaction struct { +} diff --git a/common/model/strategy.go b/common/model/strategy.go index 92aaa487..6f942d36 100644 --- a/common/model/strategy.go +++ b/common/model/strategy.go @@ -15,7 +15,8 @@ type Strategy struct { EntryPointInfo *EntryPointInfo `json:"entrypoint_info"` Description string `json:"description"` ExecuteRestriction *StrategyExecuteRestriction `json:"execute_restriction"` - Erc20TokenType global_const.TokenType + Erc20TokenType global_const.TokenType `json:"-"` + ProjectSponsor bool `json:"-"` } type PaymasterInfo struct { PayMasterAddress *common.Address `json:"paymaster_address"` @@ -41,7 +42,7 @@ func (strategy *Strategy) GetNewWork() global_const.Network { return strategy.NetWorkInfo.NetWork } -func (strategy *Strategy) GetUseToken() global_const.TokenType { +func (strategy *Strategy) GetGasToken() global_const.TokenType { return strategy.NetWorkInfo.GasToken } func (strategy *Strategy) GetPayType() global_const.PayType { diff --git a/common/network/ethereum_adaptable_executor.go b/common/network/ethereum_adaptable_executor.go index 444f4119..8a6aab91 100644 --- a/common/network/ethereum_adaptable_executor.go +++ b/common/network/ethereum_adaptable_executor.go @@ -15,6 +15,7 @@ import ( "AAStarCommunity/EthPaymaster_BackService/common/user_op" "AAStarCommunity/EthPaymaster_BackService/common/utils" "AAStarCommunity/EthPaymaster_BackService/config" + "AAStarCommunity/EthPaymaster_BackService/schedulor" "context" "crypto/ecdsa" "encoding/hex" @@ -29,6 +30,7 @@ import ( "github.com/sirupsen/logrus" "golang.org/x/xerrors" "math/big" + "strings" "sync" ) @@ -59,10 +61,12 @@ func init() { type EthereumExecutor struct { BaseExecutor - Client *ethclient.Client - GethClient *gethclient.Client - network global_const.Network - ChainId *big.Int + Client *ethclient.Client + GethClient *gethclient.Client + network global_const.Network + ChainId *big.Int + eventListener schedulor.EventListener + webSocketClient *ethclient.Client } var mu sync.Mutex @@ -92,12 +96,27 @@ func GetEthereumExecutor(network global_const.Network) *EthereumExecutor { if !success { panic(xerrors.Errorf("chainId %s is invalid", config.GetChainId(network))) } + wsUrl := config.GetNewWorkClientURl(network) + wsUrl = strings.Replace(wsUrl, "https", "wss", 1) + webSocketClient, err := ethclient.Dial(wsUrl) + if err != nil { + panic(err) + } + + eventListener, err := schedulor.NewEventListener(webSocketClient, network) + if err != nil { + panic(err) + } + go eventListener.Listen() + logrus.Debugf("after Lesten network :[%s]", network) geth := gethclient.New(client.Client()) executorMap[network] = &EthereumExecutor{ - network: network, - Client: client, - ChainId: chainId, - GethClient: geth, + network: network, + Client: client, + ChainId: chainId, + GethClient: geth, + eventListener: eventListener, + webSocketClient: webSocketClient, } return executorMap[network] @@ -539,18 +558,21 @@ func (executor *EthereumExecutor) GetUserOpHash(userOp *user_op.UserOpInput, str } } -func (executor *EthereumExecutor) GetPaymasterData(userOp *user_op.UserOpInput, strategy *model.Strategy, paymasterDataInput *paymaster_data.PaymasterDataInput) ([]byte, error) { - userOpHash, _, err := executor.GetUserOpHash(userOp, strategy) - if err != nil { +func (executor *EthereumExecutor) GetPaymasterData(userOp *user_op.UserOpInput, strategy *model.Strategy, paymasterDataInput *paymaster_data.PaymasterDataInput) (paymasterData []byte, userOpHash []byte, err error) { + userOpHash, _, hashErr := executor.GetUserOpHash(userOp, strategy) + if hashErr != nil { logrus.Errorf("GetUserOpHash error [%v]", err) - return nil, err + return nil, nil, err } signer := config.GetSigner(strategy.GetNewWork()) signature, err := utils.GetSign(userOpHash, signer.PrivateKey) if err != nil { - return nil, err + return nil, nil, err } dataGenerateFunc := paymaster_pay_type.GetGenerateFunc(strategy.GetPayType()) - paymasterData, err := dataGenerateFunc(paymasterDataInput, signature) - return paymasterData, err + paymasterData, generateDataErr := dataGenerateFunc(paymasterDataInput, signature) + if generateDataErr != nil { + return nil, nil, generateDataErr + } + return paymasterData, userOpHash, nil } diff --git a/common/network/ethereum_adaptable_executor_test.go b/common/network/ethereum_adaptable_executor_test.go index cb6197f6..07e01441 100644 --- a/common/network/ethereum_adaptable_executor_test.go +++ b/common/network/ethereum_adaptable_executor_test.go @@ -283,7 +283,7 @@ func testGetPaymasterData(t *testing.T, chain global_const.Network, input *user_ dataInput := paymaster_data.NewPaymasterDataInput(strategy) dataInput.PaymasterPostOpGasLimit = global_const.DummyPaymasterPostoperativelyBigint dataInput.PaymasterVerificationGasLimit = global_const.DummyPaymasterOversimplificationBigint - paymasterData, err := executor.GetPaymasterData(input, strategy, dataInput) + paymasterData, _, err := executor.GetPaymasterData(input, strategy, dataInput) if err != nil { t.Error(err) return @@ -301,7 +301,7 @@ func testSimulateHandleOp(t *testing.T, chain global_const.Network, strategy *mo dataInput := paymaster_data.NewPaymasterDataInput(strategy) op.AccountGasLimits = user_op.DummyAccountGasLimits op.GasFees = user_op.DummyGasFees - paymasterData, err := sepoliaExector.GetPaymasterData(op, strategy, dataInput) + paymasterData, _, err := sepoliaExector.GetPaymasterData(op, strategy, dataInput) if err != nil { t.Error(err) return @@ -338,12 +338,12 @@ func parseOpToMapV7(input user_op.UserOpInput) map[string]string { opMap := make(map[string]string) opMap["sender"] = input.Sender.String() opMap["Nonce"] = input.Nonce.String() - opMap["initCode"] = utils.EncodeToStringWithPrefix(input.InitCode[:]) - opMap["accountGasLimits"] = utils.EncodeToStringWithPrefix(input.AccountGasLimits[:]) + opMap["initCode"] = utils.EncodeToHexStringWithPrefix(input.InitCode[:]) + opMap["accountGasLimits"] = utils.EncodeToHexStringWithPrefix(input.AccountGasLimits[:]) opMap["preVerificationGas"] = input.PreVerificationGas.String() - opMap["gasFees"] = utils.EncodeToStringWithPrefix(input.GasFees[:]) - opMap["paymasterAndData"] = utils.EncodeToStringWithPrefix(input.PaymasterAndData[:]) - opMap["signature"] = utils.EncodeToStringWithPrefix(input.Signature[:]) + opMap["gasFees"] = utils.EncodeToHexStringWithPrefix(input.GasFees[:]) + opMap["paymasterAndData"] = utils.EncodeToHexStringWithPrefix(input.PaymasterAndData[:]) + opMap["signature"] = utils.EncodeToHexStringWithPrefix(input.Signature[:]) return opMap } diff --git a/common/paymaster_pay_type/paymaster_data_generate.go b/common/paymaster_pay_type/paymaster_data_generate.go index c8414179..ebc08ebc 100644 --- a/common/paymaster_pay_type/paymaster_data_generate.go +++ b/common/paymaster_pay_type/paymaster_data_generate.go @@ -38,7 +38,7 @@ var basicPaymasterDataFunc = func(data *paymaster_data.PaymasterDataInput, signa } func init() { - paymasterDataFuncMap[global_const.PayTypeVerifying] = basicPaymasterDataFunc + paymasterDataFuncMap[global_const.PayTypeProjectSponsor] = basicPaymasterDataFunc paymasterDataFuncMap[global_const.PayTypeERC20] = basicPaymasterDataFunc paymasterDataFuncMap[global_const.PayTypeSuperVerifying] = func(data *paymaster_data.PaymasterDataInput, signature []byte) ([]byte, error) { packed, err := BasicPaymasterDataAbiV06.Pack(data.ValidUntil, data.ValidAfter, data.ERC20Token, data.ExchangeRate) diff --git a/common/price_compoent/price_util.go b/common/price_compoent/price_util.go index 61b3d36a..42a7a52f 100644 --- a/common/price_compoent/price_util.go +++ b/common/price_compoent/price_util.go @@ -8,6 +8,7 @@ import ( "io" "io/ioutil" "log" + "math/big" "net/http" "net/url" "os" @@ -29,6 +30,15 @@ func init() { URLMap[global_const.TokenTypeOP] = "https://api.coingecko.com/api/v3/simple/price?ids=optimism&vs_currencies=usd" } +func GetTokenCostInUsd(tokenType global_const.TokenType, amount *big.Float) (*big.Float, error) { + price, err := GetPriceUsd(tokenType) + if err != nil { + return nil, xerrors.Errorf("get price error: %w", err) + } + amountInUsd := new(big.Float).Mul(new(big.Float).SetFloat64(price), amount) + return amountInUsd, nil +} + func GetPriceUsd(tokenType global_const.TokenType) (float64, error) { if global_const.IsStableToken(tokenType) { diff --git a/common/utils/util.go b/common/utils/util.go index 246ae7ad..25105bc6 100644 --- a/common/utils/util.go +++ b/common/utils/util.go @@ -14,6 +14,7 @@ import ( "github.com/ethereum/go-ethereum/rpc" "github.com/sirupsen/logrus" "golang.org/x/xerrors" + "gorm.io/gorm" "math/big" "regexp" "runtime" @@ -97,7 +98,7 @@ func GetGasEntryPointGasPrice(maxFeePerGas *big.Int, maxPriorityFeePerGas *big.I return GetMinValue(maxFeePerGas, combineFee) } -func EncodeToStringWithPrefix(data []byte) string { +func EncodeToHexStringWithPrefix(data []byte) string { res := hex.EncodeToString(data) if res[:2] != "0x" { return "0x" + res @@ -200,3 +201,18 @@ func GetCurrentGoroutineStack() string { n := runtime.Stack(buf[:], false) return string(buf[:n]) } +func DBTransactional(db *gorm.DB, handle func() error) (err error) { + tx := db.Begin() + defer func() { + if p := recover(); p != nil { + tx.Rollback() + panic(p) + } else if err != nil { + tx.Rollback() + } else { + err = tx.Commit().Error + } + }() + err = handle() + return +} diff --git a/common/utils/util_test.go b/common/utils/util_test.go index 1445a1b2..cb9d0f4d 100644 --- a/common/utils/util_test.go +++ b/common/utils/util_test.go @@ -41,7 +41,7 @@ func TestPackIntTo32Bytes(t *testing.T) { bytes := PackIntTo32Bytes(big.NewInt(2312), big.NewInt(2312)) - resStr := EncodeToStringWithPrefix(bytes[:]) + resStr := EncodeToHexStringWithPrefix(bytes[:]) t.Logf("resStr: %s\n", resStr) } diff --git a/config/config_test.go b/config/config_test.go index 1684e669..485e25b2 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -25,7 +25,7 @@ func TestConfigInit(t *testing.T) { t.Error("strategy is nil") return } - strategySuit, err := GetSuitableStrategy(global_const.EntrypointV06, global_const.EthereumSepolia, global_const.PayTypeVerifying) + strategySuit, err := GetSuitableStrategy(global_const.EntrypointV06, global_const.EthereumSepolia, global_const.PayTypeProjectSponsor) if err != nil { t.Error("strategySuit is nil") diff --git a/config/secret_config.go b/config/secret_config.go index 98017d11..d66b7879 100644 --- a/config/secret_config.go +++ b/config/secret_config.go @@ -40,6 +40,11 @@ func secretConfigInit(secretConfigPath string) { signerConfig[global_const.Network(network)] = eoa } } +func IsSponsorWhitelist(address string) bool { + + //TODO + return true +} func GetNetworkSecretConfig(network global_const.Network) model.NetWorkSecretConfig { return secretConfig.NetWorkSecretConfigMap[string(network)] } diff --git a/gas_executor/gas_validate.go b/gas_executor/gas_validate.go index 06fdc274..c8790d45 100644 --- a/gas_executor/gas_validate.go +++ b/gas_executor/gas_validate.go @@ -50,7 +50,7 @@ var ( ) func init() { - gasValidateFuncMap[global_const.PayTypeVerifying] = verifyingGasValidateFunc + gasValidateFuncMap[global_const.PayTypeProjectSponsor] = verifyingGasValidateFunc gasValidateFuncMap[global_const.PayTypeERC20] = erc20GasValidateFunc gasValidateFuncMap[global_const.PayTypeSuperVerifying] = superGasValidateFunc } diff --git a/rpc_server/api/v1/paymaster.go b/rpc_server/api/v1/paymaster.go index 55ec9418..97c5d8bb 100644 --- a/rpc_server/api/v1/paymaster.go +++ b/rpc_server/api/v1/paymaster.go @@ -142,7 +142,9 @@ func TryPayUserOperationMethod() MethodFunctionFunc { } logrus.Debugf("After Validate ") - if result, err := operator.TryPayUserOpExecute(request); err != nil { + apiKeyModel := ctx.MustGet(global_const.ContextKeyApiMoDel) + + if result, err := operator.TryPayUserOpExecute(apiKeyModel.(*model.ApiKeyModel), request); err != nil { return nil, xerrors.Errorf("TryPayUserOpExecute ERROR [%v]", err) } else { return result, nil diff --git a/rpc_server/api/v1/sponsor.go b/rpc_server/api/v1/sponsor.go new file mode 100644 index 00000000..35c28ebb --- /dev/null +++ b/rpc_server/api/v1/sponsor.go @@ -0,0 +1,136 @@ +package v1 + +import ( + "AAStarCommunity/EthPaymaster_BackService/common/global_const" + "AAStarCommunity/EthPaymaster_BackService/common/model" + "AAStarCommunity/EthPaymaster_BackService/sponsor_manager" + "errors" + "fmt" + "github.com/gin-gonic/gin" + "gorm.io/gorm" + "net/http" + "strconv" +) + +// DepositSponsor +// @Tags Sponsor +// @Description Deposit Sponsor +// @Accept json +// @Product json +// @Param request body DepositSponsorRequest true "DepositSponsorRequest Model +// @Param is_test_net path boolean true "Is Test Net" +// @Router /api/v1/paymaster_sponsor/deposit [post] +// @Success 200 +func DepositSponsor(ctx *gin.Context) { + request := model.DepositSponsorRequest{} + response := model.GetResponse() + if err := ctx.ShouldBindJSON(&request); err != nil { + errStr := fmt.Sprintf("Request Error [%v]", err) + response.SetHttpCode(http.StatusBadRequest).FailCode(ctx, http.StatusBadRequest, errStr) + return + } + //TODO Add Signature Verification + result, err := sponsor_manager.DepositSponsor(&request) + if err != nil { + response.SetHttpCode(http.StatusInternalServerError).FailCode(ctx, http.StatusInternalServerError, err.Error()) + return + } + response.WithDataSuccess(ctx, result) + return +} + +// WithdrawSponsor +// @Tags Sponsor +// @Description Withdraw Sponsor +// @Accept json +// @Product json +// @Param request body WithdrawSponsorRequest true "WithdrawSponsorRequest Model" +// @Param is_test_net path boolean true "Is Test Net" +// @Router /api/v1/paymaster_sponsor/withdraw [post] +// @Success 200 +func WithdrawSponsor(ctx *gin.Context) { + request := model.WithdrawSponsorRequest{} + response := model.GetResponse() + if err := ctx.ShouldBindJSON(&request); err != nil { + errStr := fmt.Sprintf("Request Error [%v]", err) + response.SetHttpCode(http.StatusBadRequest).FailCode(ctx, http.StatusBadRequest, errStr) + return + } + //TODO Add Signature Verification + result, err := sponsor_manager.WithDrawSponsor(&request) + if err != nil { + response.SetHttpCode(http.StatusInternalServerError).FailCode(ctx, http.StatusInternalServerError, err.Error()) + return + } + response.WithDataSuccess(ctx, result) + return +} + +type sponsorDepositTransaction struct { + TxHash string `json:"tx_hash"` + Amount string `json:"amount"` + UpdateType global_const.UpdateType `json:"update_type"` +} + +// GetSponsorDepositAndWithdrawTransactions +// @Tags Sponsor +// @Description Get Sponsor Deposit And Withdraw Transactions +// @Accept json +// @Product json +// @Param userId path string true "User Id" +// @Param is_test_net path boolean true "Is Test Net" +// @Router /api/v1/paymaster_sponsor/deposit_log +// @Success 200 +func GetSponsorDepositAndWithdrawTransactions(ctx *gin.Context) { + userId := ctx.Param("user_id") + textNet := ctx.Param("is_test_net") + // convertTOBool + isTestNet, _ := strconv.ParseBool(textNet) + response := model.GetResponse() + models, err := sponsor_manager.GetDepositAndWithDrawLog(userId, isTestNet) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + response.FailCode(ctx, 400, "No Deposit Transactions") + } + } + trans := make([]sponsorDepositTransaction, 0) + for _, depositModel := range models { + tran := sponsorDepositTransaction{ + TxHash: depositModel.TxHash, + Amount: depositModel.Amount.String(), + } + trans = append(trans, tran) + } + response.WithDataSuccess(ctx, trans) + return +} + +// GetSponsorMetaData +// @Tags Sponsor +// @Description Get Sponsor Balance +// @Accept json +// @Product json +// @Param userId path string true "User Id" +// @Router /api/v1/paymaster_sponsor/balance/{userId} +// @Success 200 +func GetSponsorMetaData(ctx *gin.Context) { + userId := ctx.Param("userId") + textNet := ctx.Param("is_test_net") + isTestNet, _ := strconv.ParseBool(textNet) + response := model.GetResponse() + balance, err := sponsor_manager.FindUserSponsorBalance(userId, isTestNet) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + response.FailCode(ctx, 400, "No Balance") + } + } + result := struct { + AvailableBalance string `json:"available_balance"` + SponsorAddress string `json:"sponsor_address"` + }{ + AvailableBalance: balance.AvailableBalance.String(), + SponsorAddress: balance.SponsorAddress, + } + response.WithDataSuccess(ctx, result) + return +} diff --git a/rpc_server/middlewares/rate_limit.go b/rpc_server/middlewares/rate_limit.go index b8f1c4ad..f3cfdd57 100644 --- a/rpc_server/middlewares/rate_limit.go +++ b/rpc_server/middlewares/rate_limit.go @@ -21,11 +21,9 @@ var limiter map[string]*rate.Limiter func RateLimiterByApiKeyHandler() gin.HandlerFunc { return func(ctx *gin.Context) { if exists, current := utils.CurrentUser(ctx); exists { - apiKeyModel, _ := ctx.Get(global_const.ContextKeyApiMoDel) + apiKeyModel := ctx.MustGet(global_const.ContextKeyApiMoDel) defaultLimit := DefaultLimit - if apiKeyModel != nil { - defaultLimit = apiKeyModel.(*model.ApiKeyModel).RateLimit - } + defaultLimit = apiKeyModel.(*model.ApiKeyModel).RateLimit if limiting(¤t, defaultLimit) { ctx.Next() } else { diff --git a/rpc_server/routers/routers_map.go b/rpc_server/routers/routers_map.go index de13ba27..5e4f7c56 100644 --- a/rpc_server/routers/routers_map.go +++ b/rpc_server/routers/routers_map.go @@ -13,12 +13,20 @@ func init() { PrivateRouterMaps = append(PrivateRouterMaps, RouterMap{string(Paymaster), []RestfulMethod{POST}, v1.Paymaster}) PublicRouterMaps = append(PublicRouterMaps, RouterMap{string(Auth), []RestfulMethod{POST}, api.Auth}) PublicRouterMaps = append(PublicRouterMaps, RouterMap{string(Healthz), []RestfulMethod{GET, HEAD, OPTIONS}, api.Healthz}) + PublicRouterMaps = append(PublicRouterMaps, RouterMap{string(GetSponsorLog), []RestfulMethod{GET}, v1.GetSponsorDepositAndWithdrawTransactions}) + PublicRouterMaps = append(PublicRouterMaps, RouterMap{string(DepositSponsor), []RestfulMethod{POST}, v1.DepositSponsor}) + PublicRouterMaps = append(PublicRouterMaps, RouterMap{string(WithdrawSponsor), []RestfulMethod{POST}, v1.WithdrawSponsor}) + PublicRouterMaps = append(PublicRouterMaps, RouterMap{string(GetSponsorData), []RestfulMethod{GET}, v1.GetSponsorMetaData}) } type Path string const ( - Auth Path = "api/auth" - Healthz Path = "api/healthz" - Paymaster Path = "api/v1/paymaster/:network" + Auth Path = "api/auth" + Healthz Path = "api/healthz" + Paymaster Path = "api/v1/paymaster/:network" + GetSponsorLog Path = "api/v1/paymaster_sponsor/deposit_log" + DepositSponsor Path = "api/v1/paymaster_sponsor/deposit" + WithdrawSponsor Path = "api/v1/paymaster_sponsor/withdraw" + GetSponsorData Path = "api/v1/paymaster_sponsor/data" ) diff --git a/schedulor/listener_test.go b/schedulor/listener_test.go new file mode 100644 index 00000000..b6297fdb --- /dev/null +++ b/schedulor/listener_test.go @@ -0,0 +1,137 @@ +package schedulor + +//func TestMyListener(t *testing.T) { +// if testing.Short() { +// t.Skip("skipping test in short mode.") +// } +// logrus.SetLevel(logrus.DebugLevel) +// config.InitConfig("../config/basic_strategy_config.json", "../config/basic_config.json", "../config/secret_config.json") +// client, err := ethclient.Dial("wss://optimism-sepolia.infura.io/ws/v3/0284f5a9fc55476698079b24e2f97909") +// if err != nil { +// panic(err) +// } +// listener := EventListener{ +// logCh: make(chan types.Log, 10), +// entryPointAddresses: []common.Address{common.HexToAddress("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789")}, +// paymasterAddresses: []common.Address{common.HexToAddress("0x8817340e0a3435E06254f2ed411E6418cd070D6F")}, +// client: client, +// network: global_const.OptimismSepolia, +// } +// +// listener.Listen() +//} +// +//func TestAddListener(t *testing.T) { +// if testing.Short() { +// t.Skip("skipping test in short mode.") +// } +// client, err := ethclient.Dial("wss://optimism-sepolia.infura.io/ws/v3/0284f5a9fc55476698079b24e2f97909") +// if err != nil { +// panic(err) +// } +// chainId, err := client.ChainID(context.Background()) +// if err != nil { +// panic(err) +// } +// t.Logf("chainId: %v", chainId.String()) +// entryPointAdd := common.HexToAddress("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789") +// //topics := make([][]interface{}, 0) +// eventId := userOpEvent.ID +// var eventIds []interface{} +// eventIds = append(eventIds, eventId) +// querys := make([][]interface{}, 0) +// querys = append(querys, eventIds) +// +// var userOpHashRule []interface{} +// querys = append(querys, userOpHashRule) +// var senderRule []interface{} +// querys = append(querys, senderRule) +// var paymasterRule []interface{} +// paymasterRule = append(paymasterRule, common.HexToAddress("0x8817340e0a3435E06254f2ed411E6418cd070D6F")) +// querys = append(querys, paymasterRule) +// +// //t.Log("event ", eventId.String()) +// //topics = append(topics, []common.Hash{eventId}) +// //payAddress := common.HexToAddress("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789") +// //topics[3] = []common.Hash{payAddress.Hash()} +// +// topics, err := abi.MakeTopics(querys...) +// query := ethereum.FilterQuery{ +// Addresses: []common.Address{entryPointAdd}, +// Topics: topics, +// } +// +// logs := make(chan types.Log, 11) +// +// sub, err := client.SubscribeFilterLogs(context.Background(), query, logs) +// if err != nil { +// panic(err) +// } +// +// for { +// select { +// case err := <-sub.Err(): +// t.Fatal(err) +// case vLog := <-logs: +// fmt.Println("tx Hash: ", vLog.TxHash.String()) +// if len(vLog.Topics) == 0 { +// t.Logf("vLog.Topics is empty") +// t.Fatal(err) +// } +// if vLog.Topics[0] != eventId { +// t.Logf("vLog.Topics[0] != eventId") +// t.Fatal(err) +// } +// if len(vLog.Topics) < 4 { +// t.Logf("vLog.Topics length < 4") +// t.Fatal(err) +// } +// if len(vLog.Data) > 0 { +// eventObj := &ContractUserOperationEvent{} +// err := entryPointABI.UnpackIntoInterface(eventObj, "UserOperationEvent", vLog.Data) +// t.Logf("Data: %v", hex.EncodeToString(vLog.Data)) +// if err != nil { +// t.Errorf("UnpackIntoInterface failed: %v", err) +// t.Fatal(err) +// } +// eventObj.UserOpHash = common.BytesToHash(vLog.Topics[1].Bytes()) +// eventObj.Sender = common.BytesToAddress(vLog.Topics[2].Bytes()) +// eventObj.Paymaster = common.BytesToAddress(vLog.Topics[3].Bytes()) +// +// jsonStr, _ := json.Marshal(eventObj) +// t.Logf("userOpEvent: %v", string(jsonStr)) +// } +// } +// } +//} +// +//// func TestAddListener2(t *testing.T) { +//// config.InitConfig("../config/basic_strategy_config.json", "../config/basic_config.json", "../config/secret_config.json") +//// +//// add := common.HexToAddress("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789") +//// entryPpointContract, err := executor.GetEntryPoint06(&add) +//// if err != nil { +//// t.Fatal(err) +//// } +//// it, err := entryPpointContract.FilterUserOperationEvent(&bind.FilterOpts{}, nil, nil, nil) +//// if err != nil { +//// t.Fatal(err) +//// } +//// for { +//// if it.Next() { +//// fmt.Println(it.Event) +//// } +//// } +//// } +//func TestABi(t *testing.T) { +// // 0x +// abi, _ := contract_entrypoint_v06.ContractMetaData.GetAbi() +// event := abi.Events["UserOperationEvent"] +// id := event.ID +// t.Log(id) +// +// abi07, _ := contract_entrypoint_v07.ContractMetaData.GetAbi() +// event07 := abi07.Events["UserOperationEvent"] +// id07 := event07.ID +// t.Log(id07) +//} diff --git a/schedulor/user_op_event_listener.go b/schedulor/user_op_event_listener.go new file mode 100644 index 00000000..23f86620 --- /dev/null +++ b/schedulor/user_op_event_listener.go @@ -0,0 +1,220 @@ +package schedulor + +import ( + "AAStarCommunity/EthPaymaster_BackService/common/ethereum_contract/contract/contract_entrypoint_v06" + "AAStarCommunity/EthPaymaster_BackService/common/global_const" + "AAStarCommunity/EthPaymaster_BackService/common/price_compoent" + "AAStarCommunity/EthPaymaster_BackService/config" + "AAStarCommunity/EthPaymaster_BackService/sponsor_manager" + "context" + "fmt" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/sirupsen/logrus" + "math/big" +) + +var ( + userOpEvent abi.Event + entryPointABI *abi.ABI +) + +func init() { + getAbi, _ := contract_entrypoint_v06.ContractMetaData.GetAbi() + entryPointABI = getAbi + userOpEvent = entryPointABI.Events["UserOperationEvent"] +} + +type EventListener struct { + logCh chan types.Log + entryPointAddresses []common.Address + paymasterAddresses []common.Address + client *ethclient.Client + network global_const.Network +} + +func (listener *EventListener) Listen() { + queryies := make([][]interface{}, 0) + eventId := userOpEvent.ID + var eventIds []interface{} + eventIds = append(eventIds, eventId) + queryies = append(queryies, eventIds) + var userOpHashRule []interface{} + queryies = append(queryies, userOpHashRule) + var senderRule []interface{} + queryies = append(queryies, senderRule) + var paymasterRule []interface{} + for _, paymasterAddress := range listener.paymasterAddresses { + paymasterRule = append(paymasterRule, paymasterAddress) + } + queryies = append(queryies, paymasterRule) + topics, err := abi.MakeTopics(queryies...) + if err != nil { + logrus.Errorf("abi.MakeTopics failed: %v", err) + return + } + addresses := make([]common.Address, 0) + for _, entryPointAdd := range listener.entryPointAddresses { + addresses = append(addresses, entryPointAdd) + } + + query := ethereum.FilterQuery{ + Addresses: addresses, + Topics: topics, + } + fmt.Println("query: ", query) + client := listener.client + + sub, err := client.SubscribeFilterLogs(context.Background(), query, listener.logCh) + + if err != nil { + logrus.Errorf("SubscribeFilterLogs failed: %v", err) + return + } + for { + select { + case err := <-sub.Err(): + logrus.Errorf("SubscribeFilterLogs failed: %v", err) + fmt.Println("SubscribeFilterLogs failed: ", err) + return + case vLog := <-listener.logCh: + if len(vLog.Topics) == 0 { + logrus.Errorf("vLog.Topics is empty") + continue + } + if vLog.Topics[0] != eventId { + logrus.Errorf("vLog.Topics[0] != eventId") + continue + } + if len(vLog.Topics) < 4 { + logrus.Errorf("vLog.Topics length < 4") + continue + } + if len(vLog.Data) > 0 { + eventObj := &ContractUserOperationEvent{} + err := entryPointABI.UnpackIntoInterface(eventObj, "UserOperationEvent", vLog.Data) + if err != nil { + logrus.Errorf("UnpackIntoInterface failed: %v", err) + continue + } + eventObj.UserOpHash = common.BytesToHash(vLog.Topics[1].Bytes()) + eventObj.Sender = common.BytesToAddress(vLog.Topics[2].Bytes()) + eventObj.Paymaster = common.BytesToAddress(vLog.Topics[3].Bytes()) + logrus.Debugf("UserOpEventComunicate: %v, %v, %v, %v", eventObj.UserOpHash, eventObj.Sender, eventObj.ActualGasCost, eventObj.ActualGasUsed) + UserOpEventComunicate(listener.network, *eventObj) + } + } + } + +} + +func NewEventListener(client *ethclient.Client, network global_const.Network) (EventListener, error) { + entryPointAddresses, err := config.GetSupportEntryPoints(network) + if err != nil { + return EventListener{}, err + } + entryPointAddressesArr := make([]common.Address, 2) + for _, address := range entryPointAddresses.ToSlice() { + entryPointAddressesArr = append(entryPointAddressesArr, common.HexToAddress(address)) + } + + paymasterAddresses, err := config.GetSupportPaymaster(network) + if err != nil { + return EventListener{}, err + } + paymasterAddressArr := make([]common.Address, 2) + for _, address := range paymasterAddresses.ToSlice() { + paymasterAddressArr = append(paymasterAddressArr, common.HexToAddress(address)) + } + + return EventListener{ + logCh: make(chan types.Log, 10), + entryPointAddresses: entryPointAddressesArr, + paymasterAddresses: paymasterAddressArr, + client: client, + network: network, + }, nil + //if entryPointAddress == nil { + // logrus.Debugf("Not Support Network %v", network) + // return + //} + //for _, address := range entryPointAddress.ToSlice() { + // contractAddress := common.HexToAddress(address) + // query := ethereum.FilterQuery{ + // Addresses: []common.Address{contractAddress}, + // } + // sub, err := client.SubscribeFilterLogs(nil, query, nil) + // if err != nil { + // logrus.Errorf("SubscribeFilterLogs failed: %v", err) + // return + // } + // logs := make(chan types.Log) + // go func() { + // for { + // select { + // case err := <-sub.Err(): + // logrus.Errorf("SubscribeFilterLogs failed: %v", err) + // return + // case vLog := <-logs: + // if vLog.Removed { + // continue + // } + // //TODO + // //UserOpEventComunicate(network, ContractUserOperationEvent{}) + // } + // } + // }() + // //TODO + // //client.SubscribeFilterLogs() + //} + ////TODO + +} + +type ContractUserOperationEvent struct { + UserOpHash [32]byte + Sender common.Address + Paymaster common.Address + Nonce *big.Int + Success bool + ActualGasCost *big.Int + ActualGasUsed *big.Int +} + +func UserOpEventComunicate(network global_const.Network, event ContractUserOperationEvent) { + paymasterAddressSet, _ := config.GetSupportPaymaster(network) + if paymasterAddressSet == nil { + logrus.Debugf("Not Support Network %v", network) + return + } + //if !paymasterAddressSet.Contains(event.Paymaster) { + // logrus.Debugf("UserOpEventComunicate: paymaster not support, %v", event.Paymaster) + // return + //} + if !event.Success { + _, err := sponsor_manager.ReleaseUserOpHashLockWhenFail(event.UserOpHash[:], true) + if err != nil { + logrus.Errorf("ReleaseUserOpHashLockWhenFail failed: %v", err) + } + return + } + gasCostEther := new(big.Float).SetInt(event.ActualGasCost) + gasCostEther = new(big.Float).Quo(gasCostEther, new(big.Float).SetInt64(1e18)) + //logrus.Infof("UserOpEventComunicate: %v, %v, %v, %v", event.UserOpHash, event.Sender, event.ActualGasCost, gasCostEther) + gasCostUsd, err := price_compoent.GetTokenCostInUsd(global_const.TokenTypeETH, gasCostEther) + if err != nil { + //TODO if is NetWorkError, need retry + logrus.Errorf("GetTokenCostInUsd failed: %v", err) + return + } + + _, err = sponsor_manager.ReleaseBalanceWithActualCost(event.Sender.String(), event.UserOpHash[:], gasCostUsd, true) + if err != nil { + //TODO if is NetWorkError, need retry + logrus.Errorf("ReleaseBalanceWithActualCost failed: %v", err) + return + } +} diff --git a/service/dashboard_service/dashboard_service.go b/service/dashboard_service/dashboard_service.go index 127f13e6..615a00d1 100644 --- a/service/dashboard_service/dashboard_service.go +++ b/service/dashboard_service/dashboard_service.go @@ -18,14 +18,12 @@ import ( var ( configDB *gorm.DB - relayDB *gorm.DB onlyOnce = sync.Once{} ) func Init() { onlyOnce.Do(func() { configDBDsn := config.GetConfigDBDSN() - relayDBDsn := config.GetRelayDBDSN() configDBVar, err := gorm.Open(postgres.Open(configDBDsn), &gorm.Config{}) if err != nil { @@ -33,11 +31,6 @@ func Init() { } configDB = configDBVar - relayDBVar, err := gorm.Open(postgres.Open(relayDBDsn), &gorm.Config{}) - if err != nil { - panic(err) - } - relayDB = relayDBVar }) } @@ -78,6 +71,8 @@ func GetStrategyByCode(strategyCode string, entryPointVersion global_const.Entry return nil, err } + strategy.ProjectSponsor = true + return strategy, nil } @@ -147,7 +142,7 @@ func convertStrategyDBModelToStrategy(strategyDBModel *StrategyDBModel, entryPoi }, PaymasterInfo: &model.PaymasterInfo{ PayMasterAddress: config.GetPaymasterAddress(network, entryPointVersion), - PayType: global_const.PayTypeVerifying, + PayType: global_const.PayTypeProjectSponsor, IsProjectErc20PayEnable: false, }, ExecuteRestriction: strategyExecuteRestriction, @@ -176,7 +171,7 @@ func GetSuitableStrategy(entryPointVersion global_const.EntrypointVersion, chain gasToken := config.GetGasToken(chain) entryPointAddress := config.GetEntrypointAddress(chain, entryPointVersion) paymasterAddress := config.GetPaymasterAddress(chain, entryPointVersion) - payType := global_const.PayTypeVerifying + payType := global_const.PayTypeUserSponsor isPerc20Enable := false if gasUseToken != "" { payType = global_const.PayTypeERC20 @@ -201,9 +196,6 @@ func GetSuitableStrategy(entryPointVersion global_const.EntrypointVersion, chain }, Erc20TokenType: gasUseToken, } - if strategy == nil { - return nil, errors.New("strategy not found") - } return strategy, nil } @@ -225,6 +217,7 @@ func IsPayMasterSupport(address string, chain global_const.Network) bool { type ApiKeyDbModel struct { model.BaseData + UserId int64 `gorm:"column:user_id;type:integer" json:"user_id"` Disable bool `gorm:"column:disable;type:bool" json:"disable"` ApiKey string `gorm:"column:api_key;type:varchar(255)" json:"api_key"` KeyName string `gorm:"column:key_name;type:varchar(255)" json:"key_name"` @@ -244,6 +237,7 @@ func convertApiKeyDbModelToApiKeyModel(apiKeyDbModel *ApiKeyDbModel) *model.ApiK Disable: apiKeyDbModel.Disable, ApiKey: apiKeyDbModel.ApiKey, RateLimit: 10, + UserId: apiKeyDbModel.UserId, } } func GetAPiInfoByApiKey(apiKey string) (*model.ApiKeyModel, error) { diff --git a/service/operator/operator_test.go b/service/operator/operator_test.go index a7dbdadc..521fa033 100644 --- a/service/operator/operator_test.go +++ b/service/operator/operator_test.go @@ -160,7 +160,7 @@ func testGetSupportEntrypointExecute(t *testing.T) { t.Log(res) } func testTryPayUserOpExecute(t *testing.T, request *model.UserOpRequest) { - result, err := TryPayUserOpExecute(request) + result, err := TryPayUserOpExecute(&model.ApiKeyModel{}, request) if err != nil { t.Fatal(err) return diff --git a/service/operator/try_pay_user_op_execute.go b/service/operator/try_pay_user_op_execute.go index e8fbc539..1b907362 100644 --- a/service/operator/try_pay_user_op_execute.go +++ b/service/operator/try_pay_user_op_execute.go @@ -5,17 +5,21 @@ import ( "AAStarCommunity/EthPaymaster_BackService/common/model" "AAStarCommunity/EthPaymaster_BackService/common/network" "AAStarCommunity/EthPaymaster_BackService/common/paymaster_data" + "AAStarCommunity/EthPaymaster_BackService/common/price_compoent" "AAStarCommunity/EthPaymaster_BackService/common/user_op" "AAStarCommunity/EthPaymaster_BackService/common/utils" + "AAStarCommunity/EthPaymaster_BackService/config" "AAStarCommunity/EthPaymaster_BackService/gas_executor" "AAStarCommunity/EthPaymaster_BackService/service/dashboard_service" - "AAStarCommunity/EthPaymaster_BackService/service/pay_service" "AAStarCommunity/EthPaymaster_BackService/service/validator_service" + "AAStarCommunity/EthPaymaster_BackService/sponsor_manager" "github.com/sirupsen/logrus" "golang.org/x/xerrors" + "math/big" + "strconv" ) -func TryPayUserOpExecute(request *model.UserOpRequest) (*model.TryPayUserOpResponse, error) { +func TryPayUserOpExecute(apiKeyModel *model.ApiKeyModel, request *model.UserOpRequest) (*model.TryPayUserOpResponse, error) { userOp, strategy, paymasterDataInput, err := prepareExecute(request) if err != nil { return nil, err @@ -29,12 +33,12 @@ func TryPayUserOpExecute(request *model.UserOpRequest) (*model.TryPayUserOpRespo paymasterDataInput.PaymasterVerificationGasLimit = gasResponse.OpEstimateGas.PaymasterVerificationGasLimit paymasterDataInput.PaymasterPostOpGasLimit = gasResponse.OpEstimateGas.PaymasterPostOpGasLimit - payReceipt, err := executePay(strategy, paymasterUserOp, gasResponse) - if err != nil { - return nil, err - } - logrus.Debug("payReceipt:", payReceipt) - result, err := postExecute(paymasterUserOp, strategy, gasResponse, paymasterDataInput) + //payReceipt, err := executePay(strategy, paymasterUserOp, gasResponse) + //if err != nil { + // return nil, err + //} + //logrus.Debug("payReceipt:", payReceipt) + result, err := postExecute(apiKeyModel, paymasterUserOp, strategy, gasResponse, paymasterDataInput) if err != nil { return nil, err } @@ -88,30 +92,84 @@ func ValidateGas(userOp *user_op.UserOpInput, gasComputeResponse *model.ComputeG return nil } -func executePay(strategy *model.Strategy, userOp *user_op.UserOpInput, gasResponse *model.ComputeGasResponse) (*model.PayReceipt, error) { - //1.Recharge - ethereumPayservice := pay_service.EthereumPayService{} - if err := ethereumPayservice.Pay(); err != nil { +func executePay(input *ExecutePayInput) (*model.PayResponse, error) { + if input.PayType == global_const.PayTypeERC20 { + logrus.Debugf("Not Need ExecutePay In ERC20 PayType") + return nil, nil + } + if config.IsSponsorWhitelist(input.UserOpSender) { + logrus.Debugf("Not Need ExecutePay In SponsorWhitelist [%s]", input.UserOpSender) + return nil, nil + } + // TODO + //if config.IsTestNet(input.Network) { + // logrus.Debugf("Not Need ExecutePay In TestNet [%s]", input.Network) + // return nil, nil + //} + // Get Deposit Balance + var payUserKey string + if input.ProjectSponsor == true { + payUserKey = input.ProjectUserId + } else { + payUserKey = input.UserOpSender + } + isTestNet := config.IsTestNet(input.Network) + depositBalance, err := sponsor_manager.GetAvailableBalance(payUserKey, isTestNet) + if err != nil { return nil, err } - //2.record account - ethereumPayservice.RecordAccount() - //3.return Receipt - ethereumPayservice.GetReceipt() - return &model.PayReceipt{ - TransactionHash: "0x110406d44ec1681fcdab1df2310181dee26ff43c37167b2c9c496b35cce69437", - Sponsor: "aastar", + gasUsdCost, err := price_compoent.GetTokenCostInUsd(input.GasToken, input.MaxTxGasCostInEther) + if err != nil { + return nil, err + } + if depositBalance.Cmp(gasUsdCost) < 0 { + return nil, xerrors.Errorf("Insufficient balance [%s] not Enough to Pay Cost [%s]", depositBalance.String(), gasUsdCost.String()) + } + //Lock Deposit Balance + _, err = sponsor_manager.LockUserBalance(payUserKey, input.UserOpHash, isTestNet, + gasUsdCost) + if err != nil { + return nil, err + } + + return &model.PayResponse{ + PayType: input.PayType, }, nil } -func postExecute(userOp *user_op.UserOpInput, strategy *model.Strategy, gasResponse *model.ComputeGasResponse, paymasterDataInput *paymaster_data.PaymasterDataInput) (*model.TryPayUserOpResponse, error) { +type ExecutePayInput struct { + ProjectUserId string + PayType global_const.PayType + ProjectSponsor bool + UserOpSender string + MaxTxGasCostInEther *big.Float + UserOpHash []byte + Network global_const.Network + GasToken global_const.TokenType +} + +func postExecute(apiKeyModel *model.ApiKeyModel, userOp *user_op.UserOpInput, strategy *model.Strategy, gasResponse *model.ComputeGasResponse, paymasterDataInput *paymaster_data.PaymasterDataInput) (*model.TryPayUserOpResponse, error) { + executor := network.GetEthereumExecutor(strategy.GetNewWork()) - paymasterData, err := executor.GetPaymasterData(userOp, strategy, paymasterDataInput) + paymasterData, userOpHash, err := executor.GetPaymasterData(userOp, strategy, paymasterDataInput) if err != nil { return nil, xerrors.Errorf("postExecute GetPaymasterData Error: [%w]", err) } logrus.Debug("postExecute paymasterData:", paymasterData) + _, err = executePay(&ExecutePayInput{ + ProjectUserId: strconv.FormatInt(apiKeyModel.UserId, 10), + PayType: strategy.GetPayType(), + ProjectSponsor: strategy.ProjectSponsor, + UserOpSender: userOp.Sender.String(), + MaxTxGasCostInEther: gasResponse.TotalGasDetail.MaxTxGasCostInEther, + UserOpHash: userOpHash, + Network: strategy.GetNewWork(), + GasToken: strategy.GetGasToken(), + }) + if err != nil { + return nil, xerrors.Errorf("postExecute executePay Error: [%w]", err) + } var result = &model.TryPayUserOpResponse{ StrategyId: strategy.Id, EntryPointAddress: strategy.GetEntryPointAddress().String(), @@ -121,7 +179,7 @@ func postExecute(userOp *user_op.UserOpInput, strategy *model.Strategy, gasRespo Erc20TokenCost: gasResponse.Erc20TokenCost, UserOpResponse: &model.UserOpResponse{ - PayMasterAndData: utils.EncodeToStringWithPrefix(paymasterData), + PayMasterAndData: utils.EncodeToHexStringWithPrefix(paymasterData), PreVerificationGas: gasResponse.OpEstimateGas.PreVerificationGas, MaxFeePerGas: gasResponse.OpEstimateGas.MaxFeePerGas, MaxPriorityFeePerGas: gasResponse.OpEstimateGas.MaxPriorityFeePerGas, @@ -131,8 +189,8 @@ func postExecute(userOp *user_op.UserOpInput, strategy *model.Strategy, gasRespo } if strategy.GetStrategyEntrypointVersion() == global_const.EntrypointV07 { - result.UserOpResponse.AccountGasLimit = utils.EncodeToStringWithPrefix(gasResponse.OpEstimateGas.AccountGasLimit[:]) - result.UserOpResponse.GasFees = utils.EncodeToStringWithPrefix(gasResponse.OpEstimateGas.GasFees[:]) + result.UserOpResponse.AccountGasLimit = utils.EncodeToHexStringWithPrefix(gasResponse.OpEstimateGas.AccountGasLimit[:]) + result.UserOpResponse.GasFees = utils.EncodeToHexStringWithPrefix(gasResponse.OpEstimateGas.GasFees[:]) result.UserOpResponse.PaymasterVerificationGasLimit = gasResponse.OpEstimateGas.PaymasterVerificationGasLimit result.UserOpResponse.PaymasterPostOpGasLimit = gasResponse.OpEstimateGas.PaymasterPostOpGasLimit } diff --git a/sponsor_manager/sponsor_balance_repository.go b/sponsor_manager/sponsor_balance_repository.go new file mode 100644 index 00000000..1f87f4e0 --- /dev/null +++ b/sponsor_manager/sponsor_balance_repository.go @@ -0,0 +1,78 @@ +package sponsor_manager + +import ( + "AAStarCommunity/EthPaymaster_BackService/common/model" + "database/sql/driver" + "fmt" + "math/big" +) + +type UserSponsorBalanceDBModel struct { + model.BaseData + PayUserId string `gorm:"type:varchar(255);index" json:"pay_user_id"` + AvailableBalance BigFloat `gorm:"type:numeric(30,18)" json:"available_balance"` + LockBalance BigFloat `gorm:"type:numeric(30,18)" json:"lock_balance"` + Source string `gorm:"type:varchar(255)" json:"source"` + SponsorAddress string `gorm:"type:varchar(255)" json:"sponsor_address"` + IsTestNet bool `gorm:"type:boolean" json:"is_test_net"` +} + +func (UserSponsorBalanceDBModel) TableName() string { + return "relay_user_sponsor_balance" +} + +func CreateSponsorBalance(balanceModel *UserSponsorBalanceDBModel) error { + return relayDB.Create(balanceModel).Error +} +func FindUserSponsorBalance(userId string, isTestNet bool) (balanceModel *UserSponsorBalanceDBModel, err error) { + balanceModel = &UserSponsorBalanceDBModel{} + tx := relayDB.Where("pay_user_id = ?", userId).Where("is_test_net = ?", isTestNet).First(balanceModel) + if tx.Error != nil { + return nil, tx.Error + } + return balanceModel, nil +} +func UpdateSponsor(balanceModel *UserSponsorBalanceDBModel, isTestNet bool) error { + tx := relayDB.Model(&UserSponsorBalanceDBModel{}).Where("pay_user_id = ?", balanceModel.PayUserId).Where("is_test_net = ?", isTestNet).Updates(balanceModel) + return tx.Error +} +func findUserSponsor(userId string, isTestNet bool) (balanceModel *UserSponsorBalanceDBModel, err error) { + tx := relayDB.Model(&UserSponsorBalanceDBModel{}).Where("pay_user_id = ?", userId).Where("is_test_net = ?", isTestNet).First(&balanceModel) + return balanceModel, tx.Error +} + +// BigFloat wraps big.Float to implement sql.Scanner +type BigFloat struct { + *big.Float +} + +// Scan implements the sql.Scanner interface for BigFloat +func (bf *BigFloat) Scan(value interface{}) error { + switch v := value.(type) { + case []byte: + f, _, err := big.ParseFloat(string(v), 10, 256, big.ToNearestEven) + if err != nil { + return err + } + bf.Float = f + case string: + f, _, err := big.ParseFloat(v, 10, 256, big.ToNearestEven) + if err != nil { + return err + } + bf.Float = f + case float64: + bf.Float = big.NewFloat(v) + case nil: + bf.Float = nil + default: + return fmt.Errorf("cannot scan type %T into BigFloat", value) + } + return nil +} +func (bf *BigFloat) Value() (driver.Value, error) { + if bf.Float == nil { + return nil, nil + } + return bf.Text('f', -1), nil +} diff --git a/sponsor_manager/sponsor_changelog_repository.go b/sponsor_manager/sponsor_changelog_repository.go new file mode 100644 index 00000000..768ed50d --- /dev/null +++ b/sponsor_manager/sponsor_changelog_repository.go @@ -0,0 +1,71 @@ +package sponsor_manager + +import ( + "AAStarCommunity/EthPaymaster_BackService/common/global_const" + "AAStarCommunity/EthPaymaster_BackService/common/model" + "gorm.io/datatypes" + "math/big" +) + +type UserSponsorBalanceUpdateLogDBModel struct { + model.BaseData + PayUserId string `gorm:"type:varchar(255);index" json:"pay_user_id"` + Amount BigFloat `gorm:"type:numeric(30,18)" json:"amount"` + UpdateType global_const.UpdateType `gorm:"type:varchar(20)" json:"update_type"` + UserOpHash string `gorm:"type:varchar(255)" json:"user_op_hash"` + TxHash string `gorm:"type:varchar(255)" json:"tx_hash"` + TxInfo datatypes.JSON `gorm:"type:json" json:"tx_info"` + Source string `gorm:"type:varchar(255)" json:"source"` + IsTestNet bool `gorm:"type:boolean" json:"is_test_net"` +} + +func (UserSponsorBalanceUpdateLogDBModel) TableName() string { + return "relay_user_sponsor_balance_update_log" +} + +func AddBalanceChangeLog(changeDbModel *UserSponsorBalanceUpdateLogDBModel) error { + return relayDB.Create(changeDbModel).Error +} +func LogBalanceChange(updateType global_const.UpdateType, balanceType global_const.BalanceType, data interface{}, amount *big.Float) { + + //TODO + return +} +func GetDepositAndWithDrawLog(userId string, IsTestNet bool) (models []*UserSponsorBalanceUpdateLogDBModel, err error) { + tx := relayDB.Model(&UserSponsorBalanceUpdateLogDBModel{}).Where("pay_user_id = ?", userId).Where("is_test_net = ?", IsTestNet).Where("update_type in (?)", []global_const.UpdateType{global_const.UpdateTypeDeposit, global_const.UpdateTypeWithdraw}).Find(&models) + if tx.Error != nil { + return nil, tx.Error + } + return models, nil +} +func LockBalanceChangeLog(payUserid string, userOpHash string, amount *big.Float, isTestNet bool, updateReason string) error { + logModel := &UserSponsorBalanceUpdateLogDBModel{ + PayUserId: payUserid, + Amount: BigFloat{amount}, + UserOpHash: userOpHash, + Source: "GasTank", + IsTestNet: isTestNet, + } + return relayDB.Create(logModel).Error +} + +func GetChangeModel(updateType global_const.UpdateType, payUserId string, txHash string, isTestNet bool) (ChangeModel *UserSponsorBalanceUpdateLogDBModel, err error) { + if updateType == global_const.UpdateTypeDeposit || updateType == global_const.UpdateTypeWithdraw { + + tx := relayDB.Model(ChangeModel).Where("tx_hash = ?", txHash).Where("pay_user_id", payUserId).Where("update_type = ?", global_const.UpdateTypeDeposit).Where("is_test_net", isTestNet).First(&ChangeModel) + if tx.Error != nil { + return nil, tx.Error + } else { + return ChangeModel, nil + } + } else if updateType == global_const.UpdateTypeLock || updateType == global_const.UpdateTypeRelease { + tx := relayDB.Model(ChangeModel).Where("user_op_hash = ?", txHash).Where("update_type = ?", global_const.UpdateTypeLock).Where("is_test_net", isTestNet).First(&ChangeModel) + if tx.Error != nil { + return nil, tx.Error + } else { + return ChangeModel, nil + } + } else { + return nil, nil + } +} diff --git a/sponsor_manager/sponsor_service.go b/sponsor_manager/sponsor_service.go new file mode 100644 index 00000000..009d001c --- /dev/null +++ b/sponsor_manager/sponsor_service.go @@ -0,0 +1,279 @@ +package sponsor_manager + +import ( + "AAStarCommunity/EthPaymaster_BackService/common/global_const" + "AAStarCommunity/EthPaymaster_BackService/common/model" + "AAStarCommunity/EthPaymaster_BackService/common/utils" + "AAStarCommunity/EthPaymaster_BackService/config" + "encoding/json" + "errors" + "golang.org/x/xerrors" + "gorm.io/driver/postgres" + "gorm.io/gorm" + "math/big" + "sync" +) + +type Source string + +const ( + SourceDashboard Source = "Dashboard" + SourceRacks Source = "Racks" +) + +var ( + relayDB *gorm.DB + onlyOnce = sync.Once{} +) + +func Init() { + onlyOnce.Do(func() { + relayDBDsn := config.GetRelayDBDSN() + + relayDBVar, err := gorm.Open(postgres.Open(relayDBDsn), &gorm.Config{}) + if err != nil { + panic(err) + } + relayDB = relayDBVar + }) +} + +//----------Functions---------- + +func GetAvailableBalance(userId string, isTestNet bool) (balance *big.Float, err error) { + balanceModel, err := findUserSponsor(userId, isTestNet) + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, xerrors.Errorf("No Balance Deposit Here ") + } + return nil, err + } + return balanceModel.AvailableBalance.Float, nil +} + +// LockUserBalance +// Reduce AvailableBalance and Increase LockBalance +func LockUserBalance(userId string, userOpHash []byte, isTestNet bool, + lockAmount *big.Float) (*UserSponsorBalanceUpdateLogDBModel, error) { + balanceModel, err := findUserSponsor(userId, isTestNet) + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, xerrors.Errorf("No Balance Deposit Here ") + } + if err != nil { + return nil, err + } + UserOphashStr := utils.EncodeToHexStringWithPrefix(userOpHash) + _, err = GetChangeModel(global_const.UpdateTypeLock, userId, UserOphashStr, isTestNet) + if !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, xerrors.Errorf("UserOpHash [%s] Already Lock", UserOphashStr) + } + + if balanceModel.AvailableBalance.Cmp(lockAmount) < 0 { + return nil, xerrors.Errorf("Insufficient balance [%s] not Enough to Lock [%s]", balanceModel.AvailableBalance.String(), lockAmount.String()) + } + + lockBalance := new(big.Float).Add(balanceModel.LockBalance.Float, lockAmount) + availableBalance := new(big.Float).Sub(balanceModel.AvailableBalance.Float, lockAmount) + balanceModel.LockBalance = BigFloat{lockBalance} + balanceModel.AvailableBalance = BigFloat{availableBalance} + err = utils.DBTransactional(relayDB, func() error { + if updateErr := relayDB.Model(&UserSponsorBalanceDBModel{}). + Where("pay_user_id = ?", balanceModel.PayUserId). + Where("is_test_net = ?", isTestNet).Updates(balanceModel).Error; updateErr != nil { + return err + } + + changeModel := &UserSponsorBalanceUpdateLogDBModel{ + UserOpHash: UserOphashStr, + PayUserId: userId, + Amount: BigFloat{lockAmount}, + IsTestNet: isTestNet, + UpdateType: global_const.UpdateTypeLock, + } + if createErr := relayDB.Create(changeModel).Error; createErr != nil { + return err + } + return nil + }) + + return nil, nil +} + +func ReleaseBalanceWithActualCost(userId string, userOpHash []byte, + actualGasCost *big.Float, isTestNet bool) (*UserSponsorBalanceDBModel, error) { + userOpHashHex := utils.EncodeToHexStringWithPrefix(userOpHash) + + changeModel, err := GetChangeModel(global_const.UpdateTypeLock, userId, userOpHashHex, isTestNet) + if err != nil { + return nil, err + } + balanceModel, err := findUserSponsor(changeModel.PayUserId, changeModel.IsTestNet) + + lockBalance := changeModel.Amount + balanceModel.LockBalance = BigFloat{new(big.Float).Sub(balanceModel.LockBalance.Float, lockBalance.Float)} + refundBalance := new(big.Float).Sub(lockBalance.Float, actualGasCost) + balanceModel.AvailableBalance = BigFloat{new(big.Float).Add(balanceModel.AvailableBalance.Float, refundBalance)} + + err = utils.DBTransactional(relayDB, func() error { + if updateErr := relayDB.Model(&UserSponsorBalanceDBModel{}). + Model(&UserSponsorBalanceDBModel{}). + Where("pay_user_id = ?", balanceModel.PayUserId). + Where("is_test_net = ?", isTestNet).Updates(balanceModel).Error; updateErr != nil { + return err + } + + changeDBModel := &UserSponsorBalanceUpdateLogDBModel{ + UserOpHash: userOpHashHex, + PayUserId: changeModel.PayUserId, + Amount: BigFloat{refundBalance}, + Source: "GasTank", + IsTestNet: isTestNet, + UpdateType: global_const.UpdateTypeRelease, + } + if createErr := relayDB.Create(changeDBModel).Error; createErr != nil { + return createErr + } + return nil + }) + if err != nil { + return nil, err + } + + return balanceModel, nil +} + +type ReleaseUserOpHashLockInput struct { + UserOpHash []byte +} + +func ReleaseUserOpHashLockWhenFail(userOpHash []byte, isTestNet bool) (*UserSponsorBalanceDBModel, error) { + // Get ChangeLog By UserOpHash + userOpHashHex := utils.EncodeToHexStringWithPrefix(userOpHash) + changeModel, err := GetChangeModel(global_const.UpdateTypeLock, "", userOpHashHex, isTestNet) + if err != nil { + return nil, err + } + // Release Lock + balanceModel, err := findUserSponsor(changeModel.PayUserId, changeModel.IsTestNet) + lockBalance := changeModel.Amount + balanceModel.LockBalance = BigFloat{new(big.Float).Sub(balanceModel.LockBalance.Float, lockBalance.Float)} + balanceModel.AvailableBalance = BigFloat{new(big.Float).Add(balanceModel.AvailableBalance.Float, lockBalance.Float)} + err = utils.DBTransactional(relayDB, func() error { + if updateErr := relayDB.Model(&UserSponsorBalanceDBModel{}). + Where("pay_user_id = ?", balanceModel.PayUserId). + Where("is_test_net = ?", isTestNet).Updates(balanceModel).Error; updateErr != nil { + return err + } + + changeDBModel := &UserSponsorBalanceUpdateLogDBModel{ + UserOpHash: userOpHashHex, + PayUserId: changeModel.PayUserId, + Amount: lockBalance, + IsTestNet: isTestNet, + UpdateType: global_const.UpdateTypeRelease, + } + if createErr := relayDB.Create(changeDBModel).Error; createErr != nil { + return createErr + } + return nil + }) + if err != nil { + return nil, err + } + return balanceModel, nil +} + +//----------Functions---------- + +func DepositSponsor(input *model.DepositSponsorRequest) (*UserSponsorBalanceDBModel, error) { + + balanceModel, err := FindUserSponsorBalance(input.PayUserId, input.IsTestNet) + if err != nil { + return nil, err + } + + err = utils.DBTransactional(relayDB, func() error { + if errors.Is(err, gorm.ErrRecordNotFound) { + //init Data + balanceModel = &UserSponsorBalanceDBModel{} + balanceModel.AvailableBalance = BigFloat{big.NewFloat(0)} + balanceModel.PayUserId = input.PayUserId + balanceModel.LockBalance = BigFloat{big.NewFloat(0)} + balanceModel.IsTestNet = input.IsTestNet + err = relayDB.Create(balanceModel).Error + if err != nil { + + return err + } + } + if err != nil { + + return err + } + newAvailableBalance := BigFloat{new(big.Float).Add(balanceModel.AvailableBalance.Float, input.Amount)} + balanceModel.AvailableBalance = newAvailableBalance + + if updateErr := relayDB.Model(balanceModel). + Where("pay_user_id = ?", balanceModel.PayUserId). + Where("is_test_net = ?", input.IsTestNet).Updates(balanceModel).Error; updateErr != nil { + + return updateErr + } + + txInfoJSon, _ := json.Marshal(input.TxInfo) + changeModel := &UserSponsorBalanceUpdateLogDBModel{ + PayUserId: input.PayUserId, + Amount: BigFloat{input.Amount}, + Source: "Deposit", + IsTestNet: input.IsTestNet, + UpdateType: global_const.UpdateTypeDeposit, + TxHash: input.TxHash, + TxInfo: txInfoJSon, + } + if createErr := relayDB.Create(changeModel).Error; createErr != nil { + return createErr + } + return nil + }) + if err != nil { + return nil, err + } + + return balanceModel, nil +} + +func WithDrawSponsor(input *model.WithdrawSponsorRequest) (*UserSponsorBalanceDBModel, error) { + balanceModel, err := FindUserSponsorBalance(input.PayUserId, input.IsTestNet) + if err != nil { + return nil, err + } + if balanceModel.AvailableBalance.Cmp(input.Amount) < 0 { + return nil, xerrors.Errorf("Insufficient balance [%s] not Enough to Withdraw [%s]", balanceModel.AvailableBalance.String(), input.Amount.String()) + } + newAvailableBalance := new(big.Float).Sub(balanceModel.AvailableBalance.Float, input.Amount) + balanceModel.AvailableBalance = BigFloat{newAvailableBalance} + err = utils.DBTransactional(relayDB, func() error { + if updateErr := relayDB.Model(&UserSponsorBalanceDBModel{}). + Where("pay_user_id = ?", balanceModel.PayUserId). + Where("is_test_net = ?", input.IsTestNet).Updates(balanceModel).Error; updateErr != nil { + return updateErr + } + changeModel := &UserSponsorBalanceUpdateLogDBModel{ + PayUserId: input.PayUserId, + Amount: BigFloat{input.Amount}, + Source: "Withdraw", + IsTestNet: input.IsTestNet, + UpdateType: global_const.UpdateTypeWithdraw, + TxHash: input.TxHash, + } + if createErr := relayDB.Create(changeModel).Error; createErr != nil { + return createErr + } + return nil + }) + if err != nil { + return nil, err + } + return balanceModel, nil +} diff --git a/sponsor_manager/sponsor_test.go b/sponsor_manager/sponsor_test.go new file mode 100644 index 00000000..9169f8f4 --- /dev/null +++ b/sponsor_manager/sponsor_test.go @@ -0,0 +1,132 @@ +package sponsor_manager + +import ( + "AAStarCommunity/EthPaymaster_BackService/common/model" + "AAStarCommunity/EthPaymaster_BackService/common/utils" + "AAStarCommunity/EthPaymaster_BackService/config" + + "encoding/json" + "math/big" + "testing" +) + +func TestSponsor(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + config.InitConfig("../config/basic_strategy_config.json", "../config/basic_config.json", "../config/secret_config.json") + Init() + mockUserOpHash, err := utils.DecodeStringWithPrefix("0xc0977a4ed200e8c2e62b906260e57ee2f7dca962089f8a4645117bfee3b1f215") + mockUserOpHash2, err := utils.DecodeStringWithPrefix("0xc0977a4ed200e8c2e62b906260e57ee2f7dca962089f8a4645117bfee3b1f211") + if err != nil { + t.Fatal(err) + } + tests := []struct { + name string + test func(t *testing.T) + }{ + { + "TestDepositSponsor", + func(t *testing.T) { + request := model.DepositSponsorRequest{ + PayUserId: "test", + Amount: big.NewFloat(3), + IsTestNet: true, + Source: string(SourceDashboard), + } + result, err := DepositSponsor(&request) + if err != nil { + t.Error(err) + } + if result == nil { + t.Error("DepositSponsor failed") + } + }, + }, + { + "TestGetAvaliabeBalance", + func(t *testing.T) { + testFindUserSponsor(t, "test", true) + }, + }, + { + "TestWithdrawSponsor", + func(t *testing.T) { + testWithDrawSponsor(t, "test", true, big.NewFloat(1)) + }, + }, + { + "TestLockUserBalance1", + func(t *testing.T) { + testLockUserBalance(t, "test", mockUserOpHash, true, big.NewFloat(1)) + }, + }, + { + "ReleaseUserOpHashLockWhenFail", + func(t *testing.T) { + testReleaseUserOpHashLockWhenFail(t, "test", mockUserOpHash, true) + }, + }, + { + "TestLockUserBalance2", + func(t *testing.T) { + testLockUserBalance(t, "test", mockUserOpHash2, true, big.NewFloat(1)) + }, + }, + { + "ReleaseBalanceWithActualCost", + func(t *testing.T) { + testReleaseBalanceWithActualCost(t, "test", mockUserOpHash2, true, big.NewFloat(0.5)) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, tt.test) + } +} +func testReleaseBalanceWithActualCost(t *testing.T, userId string, userOpHash []byte, isTestNet bool, actualCost *big.Float) { + _, err := ReleaseBalanceWithActualCost(userId, userOpHash, actualCost, isTestNet) + if err != nil { + t.Error(err) + } + +} +func testReleaseUserOpHashLockWhenFail(t *testing.T, userId string, userOpHash []byte, isTestNet bool) { + _, err := ReleaseUserOpHashLockWhenFail(userOpHash, isTestNet) + if err != nil { + t.Error(err) + } +} +func testLockUserBalance(t *testing.T, userId string, userOpHash []byte, isTestNet bool, lockAmount *big.Float) { + _, err := LockUserBalance(userId, userOpHash, isTestNet, lockAmount) + if err != nil { + t.Error(err) + } +} +func testWithDrawSponsor(t *testing.T, userId string, isTestNet bool, amount *big.Float) { + + request := model.WithdrawSponsorRequest{ + PayUserId: userId, + Amount: amount, + IsTestNet: isTestNet, + TxInfo: map[string]string{}, + } + result, err := WithDrawSponsor(&request) + if err != nil { + t.Error(err) + } + if result == nil { + t.Error("WithDrawSponsor failed") + } +} +func testFindUserSponsor(t *testing.T, userId string, isTestNet bool) { + sponsorModel, err := findUserSponsor(userId, isTestNet) + if err != nil { + t.Error(err) + } + if sponsorModel == nil { + t.Error("findUserSponsor failed") + } + jsonSt, _ := json.Marshal(sponsorModel) + t.Log(string(jsonSt)) +}