From f7b8b0548c754690cafc402c0087848e43937eb7 Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Mon, 23 Sep 2024 08:35:34 +0200 Subject: [PATCH 1/6] chore_: move path constants to wallet common location --- services/wallet/common/const.go | 11 +++++++++-- services/wallet/onramp/provider_mercuryo.go | 2 +- .../wallet/requests/router_input_params.go | 4 ++-- services/wallet/router/filter.go | 4 ++-- services/wallet/router/filter_test.go | 12 ++++++------ .../wallet/router/pathprocessor/constants.go | 11 ----------- .../pathprocessor/multipath_processor.go | 3 ++- .../pathprocessor/processor_bridge_celar.go | 3 ++- .../pathprocessor/processor_bridge_hop.go | 10 +++++----- .../pathprocessor/processor_ens_public_key.go | 6 +++--- .../pathprocessor/processor_ens_register.go | 4 ++-- .../pathprocessor/processor_ens_release.go | 6 +++--- .../router/pathprocessor/processor_erc1155.go | 3 ++- .../router/pathprocessor/processor_erc721.go | 3 ++- .../pathprocessor/processor_stickers_buy.go | 4 ++-- .../pathprocessor/processor_swap_paraswap.go | 10 +++++----- .../processor_swap_paraswap_test.go | 2 +- .../router/pathprocessor/processor_transfer.go | 3 ++- services/wallet/router/router.go | 18 +++++++++--------- services/wallet/router/router_helper.go | 6 +++--- services/wallet/router/routes/route.go | 9 ++++----- services/wallet/router/sendtype/send_type.go | 4 ++-- .../paraswap/request_build_transaction.go | 2 +- 23 files changed, 70 insertions(+), 70 deletions(-) diff --git a/services/wallet/common/const.go b/services/wallet/common/const.go index a3021dc83e8..3f60d44fa4a 100644 --- a/services/wallet/common/const.go +++ b/services/wallet/common/const.go @@ -1,6 +1,7 @@ package common import ( + "math/big" "strconv" "time" @@ -32,8 +33,6 @@ const ( ) var ( - ZeroAddress = ethCommon.HexToAddress("0x0000000000000000000000000000000000000000") - SupportedNetworks = map[uint64]bool{ EthereumMainnet: true, OptimismMainnet: true, @@ -56,6 +55,14 @@ const ( ContractTypeERC1155 ) +func ZeroAddress() ethCommon.Address { + return ethCommon.Address{} +} + +func ZeroBigIntValue() *big.Int { + return big.NewInt(0) +} + func (c ChainID) String() string { return strconv.FormatUint(uint64(c), 10) } diff --git a/services/wallet/onramp/provider_mercuryo.go b/services/wallet/onramp/provider_mercuryo.go index 36fb00cbf53..8e7a01e4612 100644 --- a/services/wallet/onramp/provider_mercuryo.go +++ b/services/wallet/onramp/provider_mercuryo.go @@ -122,7 +122,7 @@ func (p *MercuryoProvider) GetURL(ctx context.Context, parameters Parameters) (s widgetSecret = "AZ5fmxmrgyrXH3zre6yHU2Vw9fPqEw82" // #nosec G101 ) - if parameters.DestAddress == nil || *parameters.DestAddress == walletCommon.ZeroAddress { + if parameters.DestAddress == nil || *parameters.DestAddress == walletCommon.ZeroAddress() { return "", errors.New("destination address is required") } diff --git a/services/wallet/requests/router_input_params.go b/services/wallet/requests/router_input_params.go index b701f3ec873..1ada0b5a54e 100644 --- a/services/wallet/requests/router_input_params.go +++ b/services/wallet/requests/router_input_params.go @@ -123,8 +123,8 @@ func (i *RouteInputParams) Validate() error { if i.AmountIn != nil && i.AmountOut != nil && - i.AmountIn.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 && - i.AmountOut.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 { + i.AmountIn.ToInt().Cmp(walletCommon.ZeroBigIntValue()) > 0 && + i.AmountOut.ToInt().Cmp(walletCommon.ZeroBigIntValue()) > 0 { return ErrSwapAmountInAmountOutMustBeExclusive } diff --git a/services/wallet/router/filter.go b/services/wallet/router/filter.go index 21bc79fa6cd..bdd45b5e028 100644 --- a/services/wallet/router/filter.go +++ b/services/wallet/router/filter.go @@ -5,7 +5,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/status-im/status-go/services/wallet/common" - "github.com/status-im/status-go/services/wallet/router/pathprocessor" + walletCommon "github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/services/wallet/router/routes" "go.uber.org/zap" @@ -108,7 +108,7 @@ func setupRouteValidationMaps(fromLockedAmount map[uint64]*hexutil.Big) (map[uin fromExcluded := make(map[uint64]bool) for chainID, amount := range fromLockedAmount { - if amount.ToInt().Cmp(pathprocessor.ZeroBigIntValue) <= 0 { + if amount.ToInt().Cmp(walletCommon.ZeroBigIntValue()) <= 0 { fromExcluded[chainID] = false } else { fromIncluded[chainID] = false diff --git a/services/wallet/router/filter_test.go b/services/wallet/router/filter_test.go index ae0a974e729..e7f1a4b3abb 100644 --- a/services/wallet/router/filter_test.go +++ b/services/wallet/router/filter_test.go @@ -7,7 +7,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/status-im/status-go/params" - "github.com/status-im/status-go/services/wallet/router/pathprocessor" + walletCommon "github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/services/wallet/router/routes" "github.com/stretchr/testify/assert" @@ -94,9 +94,9 @@ func TestSetupRouteValidationMaps(t *testing.T) { { name: "Mixed zero and non-zero amounts", fromLockedAmount: map[uint64]*hexutil.Big{ - 1: (*hexutil.Big)(pathprocessor.ZeroBigIntValue), + 1: (*hexutil.Big)(walletCommon.ZeroBigIntValue()), 2: (*hexutil.Big)(big.NewInt(200)), - 3: (*hexutil.Big)(pathprocessor.ZeroBigIntValue), + 3: (*hexutil.Big)(walletCommon.ZeroBigIntValue()), 4: (*hexutil.Big)(big.NewInt(400)), }, expectedIncluded: map[uint64]bool{ @@ -123,8 +123,8 @@ func TestSetupRouteValidationMaps(t *testing.T) { { name: "All zero amounts", fromLockedAmount: map[uint64]*hexutil.Big{ - 1: (*hexutil.Big)(pathprocessor.ZeroBigIntValue), - 2: (*hexutil.Big)(pathprocessor.ZeroBigIntValue), + 1: (*hexutil.Big)(walletCommon.ZeroBigIntValue()), + 2: (*hexutil.Big)(walletCommon.ZeroBigIntValue()), }, expectedIncluded: map[uint64]bool{}, expectedExcluded: map[uint64]bool{ @@ -145,7 +145,7 @@ func TestSetupRouteValidationMaps(t *testing.T) { { name: "Single zero amount", fromLockedAmount: map[uint64]*hexutil.Big{ - 1: (*hexutil.Big)(pathprocessor.ZeroBigIntValue), + 1: (*hexutil.Big)(walletCommon.ZeroBigIntValue()), }, expectedIncluded: map[uint64]bool{}, expectedExcluded: map[uint64]bool{ diff --git a/services/wallet/router/pathprocessor/constants.go b/services/wallet/router/pathprocessor/constants.go index 984ac6ac485..0d4ad4a9a83 100644 --- a/services/wallet/router/pathprocessor/constants.go +++ b/services/wallet/router/pathprocessor/constants.go @@ -1,16 +1,5 @@ package pathprocessor -import ( - "math/big" - - "github.com/ethereum/go-ethereum/common" -) - -var ( - ZeroAddress = common.Address{} - ZeroBigIntValue = big.NewInt(0) -) - const ( IncreaseEstimatedGasFactor = 1.1 SevenDaysInSeconds = 60 * 60 * 24 * 7 diff --git a/services/wallet/router/pathprocessor/multipath_processor.go b/services/wallet/router/pathprocessor/multipath_processor.go index f34475d4213..a80f8d91cb5 100644 --- a/services/wallet/router/pathprocessor/multipath_processor.go +++ b/services/wallet/router/pathprocessor/multipath_processor.go @@ -4,6 +4,7 @@ import ( "math/big" "github.com/status-im/status-go/eth-node/types" + walletCommon "github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/transactions" ) @@ -31,7 +32,7 @@ func (t *MultipathProcessorTxArgs) Value() *big.Int { return t.ERC1155TransferTx.Amount.ToInt() } - return ZeroBigIntValue + return walletCommon.ZeroBigIntValue() } func (t *MultipathProcessorTxArgs) From() types.Address { diff --git a/services/wallet/router/pathprocessor/processor_bridge_celar.go b/services/wallet/router/pathprocessor/processor_bridge_celar.go index c1dc8520130..3e187f07f86 100644 --- a/services/wallet/router/pathprocessor/processor_bridge_celar.go +++ b/services/wallet/router/pathprocessor/processor_bridge_celar.go @@ -23,6 +23,7 @@ import ( "github.com/status-im/status-go/rpc" "github.com/status-im/status-go/params" + walletCommon "github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/services/wallet/router/pathprocessor/cbridge" "github.com/status-im/status-go/services/wallet/thirdparty" "github.com/status-im/status-go/services/wallet/token" @@ -203,7 +204,7 @@ func (s *CelerBridgeProcessor) CalculateFees(params ProcessorInputParams) (*big. return nil, nil, ErrFailedToParsePercentageFee } - return ZeroBigIntValue, new(big.Int).Add(baseFee, percFee), nil + return walletCommon.ZeroBigIntValue(), new(big.Int).Add(baseFee, percFee), nil } func (c *CelerBridgeProcessor) PackTxInputData(params ProcessorInputParams) ([]byte, error) { diff --git a/services/wallet/router/pathprocessor/processor_bridge_hop.go b/services/wallet/router/pathprocessor/processor_bridge_hop.go index 7fe327b06b7..6753d450af7 100644 --- a/services/wallet/router/pathprocessor/processor_bridge_hop.go +++ b/services/wallet/router/pathprocessor/processor_bridge_hop.go @@ -377,7 +377,7 @@ func (h *HopBridgeProcessor) CalculateFees(params ProcessorInputParams) (*big.In Deadline: time.Now().Add(SevenDaysInSeconds).Unix(), } h.bonderFee.Store(bonderKey, bonderFee) - return val, ZeroBigIntValue, nil + return val, walletCommon.ZeroBigIntValue(), nil } return nil, nil, ErrNoBonderFeeFound } @@ -471,7 +471,7 @@ func (h *HopBridgeProcessor) packL1BridgeTx(abi abi.ABI, toChainID uint64, to co bonderFee.AmountOutMin.Int, big.NewInt(bonderFee.Deadline), common.Address{}, - ZeroBigIntValue) + walletCommon.ZeroBigIntValue) } func (h *HopBridgeProcessor) sendL1BridgeTx(contractAddress common.Address, ethClient chain.ClientInterface, toChainID uint64, @@ -493,7 +493,7 @@ func (h *HopBridgeProcessor) sendL1BridgeTx(contractAddress common.Address, ethC bonderFee.AmountOutMin.Int, big.NewInt(bonderFee.Deadline), common.Address{}, - ZeroBigIntValue) + walletCommon.ZeroBigIntValue()) } if token.Symbol == HopSymbol { @@ -513,7 +513,7 @@ func (h *HopBridgeProcessor) sendL1BridgeTx(contractAddress common.Address, ethC bonderFee.AmountOutMin.Int, big.NewInt(bonderFee.Deadline), common.Address{}, - ZeroBigIntValue) + walletCommon.ZeroBigIntValue()) } contractInstance, err := hopL1Erc20Bridge.NewHopL1Erc20Bridge( @@ -532,7 +532,7 @@ func (h *HopBridgeProcessor) sendL1BridgeTx(contractAddress common.Address, ethC bonderFee.AmountOutMin.Int, big.NewInt(bonderFee.Deadline), common.Address{}, - ZeroBigIntValue) + walletCommon.ZeroBigIntValue()) } diff --git a/services/wallet/router/pathprocessor/processor_ens_public_key.go b/services/wallet/router/pathprocessor/processor_ens_public_key.go index 78172783507..b0134439cc1 100644 --- a/services/wallet/router/pathprocessor/processor_ens_public_key.go +++ b/services/wallet/router/pathprocessor/processor_ens_public_key.go @@ -48,7 +48,7 @@ func (s *ENSPublicKeyProcessor) AvailableFor(params ProcessorInputParams) (bool, } func (s *ENSPublicKeyProcessor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) { - return ZeroBigIntValue, ZeroBigIntValue, nil + return walletCommon.ZeroBigIntValue(), walletCommon.ZeroBigIntValue(), nil } func (s *ENSPublicKeyProcessor) PackTxInputData(params ProcessorInputParams) ([]byte, error) { @@ -89,7 +89,7 @@ func (s *ENSPublicKeyProcessor) EstimateGas(params ProcessorInputParams) (uint64 msg := ethereum.CallMsg{ From: params.FromAddr, To: &contractAddress, - Value: ZeroBigIntValue, + Value: walletCommon.ZeroBigIntValue(), Data: input, } @@ -120,7 +120,7 @@ func (s *ENSPublicKeyProcessor) GetContractAddress(params ProcessorInputParams) if err != nil { return common.Address{}, createENSPublicKeyErrorResponse(err) } - if *addr == ZeroAddress { + if *addr == walletCommon.ZeroAddress() { return common.Address{}, ErrENSResolverNotFound } return *addr, nil diff --git a/services/wallet/router/pathprocessor/processor_ens_register.go b/services/wallet/router/pathprocessor/processor_ens_register.go index 29ec6bfca92..fef94839d6c 100644 --- a/services/wallet/router/pathprocessor/processor_ens_register.go +++ b/services/wallet/router/pathprocessor/processor_ens_register.go @@ -64,7 +64,7 @@ func (s *ENSRegisterProcessor) AvailableFor(params ProcessorInputParams) (bool, } func (s *ENSRegisterProcessor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) { - return ZeroBigIntValue, ZeroBigIntValue, nil + return walletCommon.ZeroBigIntValue(), walletCommon.ZeroBigIntValue(), nil } func (s *ENSRegisterProcessor) PackTxInputData(params ProcessorInputParams) ([]byte, error) { @@ -125,7 +125,7 @@ func (s *ENSRegisterProcessor) EstimateGas(params ProcessorInputParams) (uint64, msg := ethereum.CallMsg{ From: params.FromAddr, To: &contractAddress, - Value: ZeroBigIntValue, + Value: walletCommon.ZeroBigIntValue(), Data: input, } diff --git a/services/wallet/router/pathprocessor/processor_ens_release.go b/services/wallet/router/pathprocessor/processor_ens_release.go index 3e9f5943b25..6f0d56aa2dc 100644 --- a/services/wallet/router/pathprocessor/processor_ens_release.go +++ b/services/wallet/router/pathprocessor/processor_ens_release.go @@ -48,7 +48,7 @@ func (s *ENSReleaseProcessor) AvailableFor(params ProcessorInputParams) (bool, e } func (s *ENSReleaseProcessor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) { - return ZeroBigIntValue, ZeroBigIntValue, nil + return walletCommon.ZeroBigIntValue(), walletCommon.ZeroBigIntValue(), nil } func (s *ENSReleaseProcessor) PackTxInputData(params ProcessorInputParams) ([]byte, error) { @@ -88,7 +88,7 @@ func (s *ENSReleaseProcessor) EstimateGas(params ProcessorInputParams) (uint64, msg := ethereum.CallMsg{ From: params.FromAddr, To: &contractAddress, - Value: ZeroBigIntValue, + Value: walletCommon.ZeroBigIntValue(), Data: input, } @@ -119,7 +119,7 @@ func (s *ENSReleaseProcessor) GetContractAddress(params ProcessorInputParams) (c if err != nil { return common.Address{}, err } - if addr == ZeroAddress { + if addr == walletCommon.ZeroAddress() { return common.Address{}, ErrENSRegistrarNotFound } return addr, nil diff --git a/services/wallet/router/pathprocessor/processor_erc1155.go b/services/wallet/router/pathprocessor/processor_erc1155.go index 02d50a68843..1f8b7d43c73 100644 --- a/services/wallet/router/pathprocessor/processor_erc1155.go +++ b/services/wallet/router/pathprocessor/processor_erc1155.go @@ -16,6 +16,7 @@ import ( "github.com/status-im/status-go/contracts/ierc1155" "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/rpc" + walletCommon "github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/transactions" ) @@ -48,7 +49,7 @@ func (s *ERC1155Processor) AvailableFor(params ProcessorInputParams) (bool, erro } func (s *ERC1155Processor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) { - return ZeroBigIntValue, ZeroBigIntValue, nil + return walletCommon.ZeroBigIntValue(), walletCommon.ZeroBigIntValue(), nil } func (s *ERC1155Processor) PackTxInputData(params ProcessorInputParams) ([]byte, error) { diff --git a/services/wallet/router/pathprocessor/processor_erc721.go b/services/wallet/router/pathprocessor/processor_erc721.go index 35f5814a4f7..5591596bba8 100644 --- a/services/wallet/router/pathprocessor/processor_erc721.go +++ b/services/wallet/router/pathprocessor/processor_erc721.go @@ -18,6 +18,7 @@ import ( "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/params" "github.com/status-im/status-go/rpc" + walletCommon "github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/services/wallet/token" "github.com/status-im/status-go/transactions" ) @@ -55,7 +56,7 @@ func (s *ERC721Processor) AvailableFor(params ProcessorInputParams) (bool, error } func (s *ERC721Processor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) { - return ZeroBigIntValue, ZeroBigIntValue, nil + return walletCommon.ZeroBigIntValue(), walletCommon.ZeroBigIntValue(), nil } func (s *ERC721Processor) packTxInputDataInternally(params ProcessorInputParams, functionName string) ([]byte, error) { diff --git a/services/wallet/router/pathprocessor/processor_stickers_buy.go b/services/wallet/router/pathprocessor/processor_stickers_buy.go index 5db9d96284a..8e83dbd1af0 100644 --- a/services/wallet/router/pathprocessor/processor_stickers_buy.go +++ b/services/wallet/router/pathprocessor/processor_stickers_buy.go @@ -50,7 +50,7 @@ func (s *StickersBuyProcessor) AvailableFor(params ProcessorInputParams) (bool, } func (s *StickersBuyProcessor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) { - return ZeroBigIntValue, ZeroBigIntValue, nil + return walletCommon.ZeroBigIntValue(), walletCommon.ZeroBigIntValue(), nil } func (s *StickersBuyProcessor) PackTxInputData(params ProcessorInputParams) ([]byte, error) { @@ -117,7 +117,7 @@ func (s *StickersBuyProcessor) EstimateGas(params ProcessorInputParams) (uint64, msg := ethereum.CallMsg{ From: params.FromAddr, To: &contractAddress, - Value: ZeroBigIntValue, + Value: walletCommon.ZeroBigIntValue(), Data: input, } diff --git a/services/wallet/router/pathprocessor/processor_swap_paraswap.go b/services/wallet/router/pathprocessor/processor_swap_paraswap.go index a9b92a48566..991c2a59183 100644 --- a/services/wallet/router/pathprocessor/processor_swap_paraswap.go +++ b/services/wallet/router/pathprocessor/processor_swap_paraswap.go @@ -109,8 +109,8 @@ func (s *SwapParaswapProcessor) AvailableFor(params ProcessorInputParams) (bool, s.paraswapClient.SetPartnerAddress(partnerAddress) s.paraswapClient.SetPartnerFeePcnt(partnerFeePcnt) - searchForToken := params.FromToken.Address == ZeroAddress - searchForToToken := params.ToToken.Address == ZeroAddress + searchForToken := params.FromToken.Address == walletCommon.ZeroAddress() + searchForToToken := params.ToToken.Address == walletCommon.ZeroAddress() if searchForToToken || searchForToken { tokensList, err := s.paraswapClient.FetchTokensList(context.Background()) if err != nil { @@ -136,7 +136,7 @@ func (s *SwapParaswapProcessor) AvailableFor(params ProcessorInputParams) (bool, } } - if params.FromToken.Address == ZeroAddress || params.ToToken.Address == ZeroAddress { + if params.FromToken.Address == walletCommon.ZeroAddress() || params.ToToken.Address == walletCommon.ZeroAddress() { return false, ErrCannotResolveTokens } @@ -161,7 +161,7 @@ func calcReceivedAmountAndFee(baseDestAmount *big.Int, feePcnt float64) (destAmo } func (s *SwapParaswapProcessor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) { - return ZeroBigIntValue, ZeroBigIntValue, nil + return walletCommon.ZeroBigIntValue(), walletCommon.ZeroBigIntValue(), nil } func (s *SwapParaswapProcessor) PackTxInputData(params ProcessorInputParams) ([]byte, error) { @@ -180,7 +180,7 @@ func (s *SwapParaswapProcessor) EstimateGas(params ProcessorInputParams) (uint64 } swapSide := paraswap.SellSide - if params.AmountOut != nil && params.AmountOut.Cmp(ZeroBigIntValue) > 0 { + if params.AmountOut != nil && params.AmountOut.Cmp(walletCommon.ZeroBigIntValue()) > 0 { swapSide = paraswap.BuySide } diff --git a/services/wallet/router/pathprocessor/processor_swap_paraswap_test.go b/services/wallet/router/pathprocessor/processor_swap_paraswap_test.go index 578efb5ce68..07a2ec2f1b1 100644 --- a/services/wallet/router/pathprocessor/processor_swap_paraswap_test.go +++ b/services/wallet/router/pathprocessor/processor_swap_paraswap_test.go @@ -56,7 +56,7 @@ func TestParaswapWithPartnerFee(t *testing.T) { partnerAddress, partnerFeePcnt := getPartnerAddressAndFeePcnt(chainID) - if partnerAddress != walletCommon.ZeroAddress { + if partnerAddress != walletCommon.ZeroAddress() { require.Greater(t, partnerFeePcnt, 0.0) expectedFee := uint64(float64(testPriceRoute.DestAmount.Uint64()) * partnerFeePcnt / 100.0) diff --git a/services/wallet/router/pathprocessor/processor_transfer.go b/services/wallet/router/pathprocessor/processor_transfer.go index a6a3e378118..4fa056eb4de 100644 --- a/services/wallet/router/pathprocessor/processor_transfer.go +++ b/services/wallet/router/pathprocessor/processor_transfer.go @@ -13,6 +13,7 @@ import ( "github.com/status-im/status-go/contracts/ierc20" "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/rpc" + walletCommon "github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/transactions" ) @@ -47,7 +48,7 @@ func (s *TransferProcessor) AvailableFor(params ProcessorInputParams) (bool, err } func (s *TransferProcessor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) { - return ZeroBigIntValue, ZeroBigIntValue, nil + return walletCommon.ZeroBigIntValue(), walletCommon.ZeroBigIntValue(), nil } func (s *TransferProcessor) PackTxInputData(params ProcessorInputParams) ([]byte, error) { diff --git a/services/wallet/router/router.go b/services/wallet/router/router.go index 6a551ebb86c..86dc58684c0 100644 --- a/services/wallet/router/router.go +++ b/services/wallet/router/router.go @@ -242,7 +242,7 @@ func (r *Router) SuggestedRoutes(ctx context.Context, input *requests.RouteInput // return only if there are no balances, otherwise try to resolve the candidates for chains we know the balances for noBalanceOnAnyChain := true r.activeBalanceMap.Range(func(key, value interface{}) bool { - if value.(*big.Int).Cmp(pathprocessor.ZeroBigIntValue) > 0 { + if value.(*big.Int).Cmp(walletCommon.ZeroBigIntValue()) > 0 { noBalanceOnAnyChain = false return false } @@ -406,7 +406,7 @@ func (r *Router) getOptionsForAmoutToSplitAccrossChainsForProcessingChain(input continue } - if tokenBalance.Cmp(pathprocessor.ZeroBigIntValue) > 0 { + if tokenBalance.Cmp(walletCommon.ZeroBigIntValue()) > 0 { if tokenBalance.Cmp(amountToSplit) <= 0 { crossChainAmountOptions[chain.ChainID] = append(crossChainAmountOptions[chain.ChainID], amountOption{ amount: tokenBalance, @@ -414,7 +414,7 @@ func (r *Router) getOptionsForAmoutToSplitAccrossChainsForProcessingChain(input subtractFees: true, // for chains where we're taking the full balance, we want to subtract the fees }) amountToSplit = new(big.Int).Sub(amountToSplit, tokenBalance) - } else if amountToSplit.Cmp(pathprocessor.ZeroBigIntValue) > 0 { + } else if amountToSplit.Cmp(walletCommon.ZeroBigIntValue()) > 0 { crossChainAmountOptions[chain.ChainID] = append(crossChainAmountOptions[chain.ChainID], amountOption{ amount: amountToSplit, locked: false, @@ -438,7 +438,7 @@ func (r *Router) getCrossChainsOptionsForSendingAmount(input *requests.RouteInpu amountLocked := false amountToSend := input.AmountIn.ToInt() - if amountToSend.Cmp(pathprocessor.ZeroBigIntValue) == 0 { + if amountToSend.Cmp(walletCommon.ZeroBigIntValue()) == 0 { finalCrossChainAmountOptions[selectedFromChain.ChainID] = append(finalCrossChainAmountOptions[selectedFromChain.ChainID], amountOption{ amount: amountToSend, locked: false, @@ -459,7 +459,7 @@ func (r *Router) getCrossChainsOptionsForSendingAmount(input *requests.RouteInpu } } - if amountToSend.Cmp(pathprocessor.ZeroBigIntValue) > 0 { + if amountToSend.Cmp(walletCommon.ZeroBigIntValue()) > 0 { // add full amount always, cause we want to check for balance errors at the end of the routing algorithm // TODO: once we introduce bettwer error handling and start checking for the balance at the beginning of the routing algorithm // we can remove this line and optimize the routing algorithm more @@ -796,7 +796,7 @@ func (r *Router) checkBalancesForTheBestRoute(ctx context.Context, bestRoute rou for _, path := range bestRoute { tokenKey := makeBalanceKey(path.FromChain.ChainID, path.FromToken.Symbol) if tokenBalance, ok := balanceMapCopy[tokenKey]; ok { - if tokenBalance.Cmp(pathprocessor.ZeroBigIntValue) > 0 { + if tokenBalance.Cmp(walletCommon.ZeroBigIntValue()) > 0 { hasPositiveBalance = true } } @@ -807,7 +807,7 @@ func (r *Router) checkBalancesForTheBestRoute(ctx context.Context, bestRoute rou } } - if path.RequiredTokenBalance != nil && path.RequiredTokenBalance.Cmp(pathprocessor.ZeroBigIntValue) > 0 { + if path.RequiredTokenBalance != nil && path.RequiredTokenBalance.Cmp(walletCommon.ZeroBigIntValue()) > 0 { if tokenBalance, ok := balanceMapCopy[tokenKey]; ok { if tokenBalance.Cmp(path.RequiredTokenBalance) == -1 { err := &errors.ErrorResponse{ @@ -911,12 +911,12 @@ func (r *Router) resolveRoutes(ctx context.Context, input *requests.RouteInputPa for _, path := range bestRoute { if path.SubtractFees && path.FromToken.IsNative() { path.AmountIn.ToInt().Sub(path.AmountIn.ToInt(), path.TxFee.ToInt()) - if path.TxL1Fee.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 { + if path.TxL1Fee.ToInt().Cmp(walletCommon.ZeroBigIntValue()) > 0 { path.AmountIn.ToInt().Sub(path.AmountIn.ToInt(), path.TxL1Fee.ToInt()) } if path.ApprovalRequired { path.AmountIn.ToInt().Sub(path.AmountIn.ToInt(), path.ApprovalFee.ToInt()) - if path.ApprovalL1Fee.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 { + if path.ApprovalL1Fee.ToInt().Cmp(walletCommon.ZeroBigIntValue()) > 0 { path.AmountIn.ToInt().Sub(path.AmountIn.ToInt(), path.ApprovalL1Fee.ToInt()) } } diff --git a/services/wallet/router/router_helper.go b/services/wallet/router/router_helper.go index b373ccff116..ee1ae45565a 100644 --- a/services/wallet/router/router_helper.go +++ b/services/wallet/router/router_helper.go @@ -44,7 +44,7 @@ func (r *Router) requireApproval(ctx context.Context, sendType sendtype.SendType return false, nil, err } - if approvalContractAddress == nil || *approvalContractAddress == pathprocessor.ZeroAddress { + if approvalContractAddress == nil || *approvalContractAddress == walletCommon.ZeroAddress() { return false, nil, nil } @@ -68,7 +68,7 @@ func (r *Router) requireApproval(ctx context.Context, sendType sendtype.SendType } func (r *Router) packApprovalInputData(amountIn *big.Int, approvalContractAddress *common.Address) ([]byte, error) { - if approvalContractAddress == nil || *approvalContractAddress == pathprocessor.ZeroAddress { + if approvalContractAddress == nil || *approvalContractAddress == walletCommon.ZeroAddress { return []byte{}, nil } @@ -94,7 +94,7 @@ func (r *Router) estimateGasForApproval(params pathprocessor.ProcessorInputParam return ethClient.EstimateGas(context.Background(), ethereum.CallMsg{ From: params.FromAddr, To: ¶ms.FromToken.Address, - Value: pathprocessor.ZeroBigIntValue, + Value: walletCommon.ZeroBigIntValue(), Data: data, }) } diff --git a/services/wallet/router/routes/route.go b/services/wallet/router/routes/route.go index eee24b00711..aa00ae00291 100644 --- a/services/wallet/router/routes/route.go +++ b/services/wallet/router/routes/route.go @@ -5,7 +5,6 @@ import ( "math/big" "github.com/status-im/status-go/services/wallet/common" - "github.com/status-im/status-go/services/wallet/router/pathprocessor" ) type Route []*Path @@ -25,19 +24,19 @@ func FindBestRoute(routes []Route, tokenPrice float64, nativeTokenPrice float64) txFeeInEth := common.GweiToEth(common.WeiToGwei(path.TxFee.ToInt())) pathCost := new(big.Float).Mul(txFeeInEth, nativeTokenPrice) - if path.TxL1Fee.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 { + if path.TxL1Fee.ToInt().Cmp(common.ZeroBigIntValue()) > 0 { txL1FeeInEth := common.GweiToEth(common.WeiToGwei(path.TxL1Fee.ToInt())) pathCost.Add(pathCost, new(big.Float).Mul(txL1FeeInEth, nativeTokenPrice)) } - if path.TxBonderFees != nil && path.TxBonderFees.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 { + if path.TxBonderFees != nil && path.TxBonderFees.ToInt().Cmp(common.ZeroBigIntValue()) > 0 { pathCost.Add(pathCost, new(big.Float).Mul( new(big.Float).Quo(new(big.Float).SetInt(path.TxBonderFees.ToInt()), tokenDenominator), new(big.Float).SetFloat64(tokenPrice))) } - if path.TxTokenFees != nil && path.TxTokenFees.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 && path.FromToken != nil { + if path.TxTokenFees != nil && path.TxTokenFees.ToInt().Cmp(common.ZeroBigIntValue()) > 0 && path.FromToken != nil { pathCost.Add(pathCost, new(big.Float).Mul( new(big.Float).Quo(new(big.Float).SetInt(path.TxTokenFees.ToInt()), tokenDenominator), new(big.Float).SetFloat64(tokenPrice))) @@ -48,7 +47,7 @@ func FindBestRoute(routes []Route, tokenPrice float64, nativeTokenPrice float64) approvalFeeInEth := common.GweiToEth(common.WeiToGwei(path.ApprovalFee.ToInt())) pathCost.Add(pathCost, new(big.Float).Mul(approvalFeeInEth, nativeTokenPrice)) - if path.ApprovalL1Fee.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 { + if path.ApprovalL1Fee.ToInt().Cmp(common.ZeroBigIntValue()) > 0 { approvalL1FeeInEth := common.GweiToEth(common.WeiToGwei(path.ApprovalL1Fee.ToInt())) pathCost.Add(pathCost, new(big.Float).Mul(approvalL1FeeInEth, nativeTokenPrice)) } diff --git a/services/wallet/router/sendtype/send_type.go b/services/wallet/router/sendtype/send_type.go index 6018db6581f..c6f8381a96c 100644 --- a/services/wallet/router/sendtype/send_type.go +++ b/services/wallet/router/sendtype/send_type.go @@ -119,13 +119,13 @@ func (s SendType) CanUseProcessor(p pathprocessor.PathProcessor) bool { } func (s SendType) ProcessZeroAmountInProcessor(amountIn *big.Int, amountOut *big.Int, processorName string) bool { - if amountIn.Cmp(pathprocessor.ZeroBigIntValue) == 0 { + if amountIn.Cmp(walletCommon.ZeroBigIntValue()) == 0 { if s == Transfer { if processorName != pathprocessor.ProcessorTransferName { return false } } else if s == Swap { - if amountOut.Cmp(pathprocessor.ZeroBigIntValue) == 0 { + if amountOut.Cmp(walletCommon.ZeroBigIntValue()) == 0 { return false } } else { diff --git a/services/wallet/thirdparty/paraswap/request_build_transaction.go b/services/wallet/thirdparty/paraswap/request_build_transaction.go index b1f80e47b4d..1ea433cb255 100644 --- a/services/wallet/thirdparty/paraswap/request_build_transaction.go +++ b/services/wallet/thirdparty/paraswap/request_build_transaction.go @@ -50,7 +50,7 @@ func (c *ClientV5) BuildTransaction(ctx context.Context, srcTokenAddress common. params["destAmount"] = destAmountWei.String() } params["partner"] = c.partnerID - if c.partnerAddress != walletCommon.ZeroAddress && c.partnerFeePcnt > 0 { + if c.partnerAddress != walletCommon.ZeroAddress() && c.partnerFeePcnt > 0 { params["partnerAddress"] = c.partnerAddress.Hex() params["partnerFeeBps"] = uint(c.partnerFeePcnt * 100) } From b1dec2667f288f13381a6b55071ed852ab598675 Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Mon, 23 Sep 2024 09:06:55 +0200 Subject: [PATCH 2/6] chore_: move pack approval and get token id functions to wallet common helper --- services/wallet/common/helpers.go | 32 +++++++++++++++++++ services/wallet/common/helpers_test.go | 30 +++++++++++++++++ .../router/pathprocessor/processor_erc1155.go | 7 ++-- .../router/pathprocessor/processor_erc721.go | 7 ++-- services/wallet/router/router_helper.go | 20 ++---------- 5 files changed, 70 insertions(+), 26 deletions(-) create mode 100644 services/wallet/common/helpers.go create mode 100644 services/wallet/common/helpers_test.go diff --git a/services/wallet/common/helpers.go b/services/wallet/common/helpers.go new file mode 100644 index 00000000000..02d18c99437 --- /dev/null +++ b/services/wallet/common/helpers.go @@ -0,0 +1,32 @@ +package common + +import ( + "fmt" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/status-im/status-go/contracts/ierc20" +) + +func PackApprovalInputData(amountIn *big.Int, approvalContractAddress *common.Address) ([]byte, error) { + if approvalContractAddress == nil || *approvalContractAddress == ZeroAddress() { + return []byte{}, nil + } + + erc20ABI, err := abi.JSON(strings.NewReader(ierc20.IERC20ABI)) + if err != nil { + return []byte{}, err + } + + return erc20ABI.Pack("approve", approvalContractAddress, amountIn) +} + +func GetTokenIdFromSymbol(symbol string) (*big.Int, error) { + id, success := big.NewInt(0).SetString(symbol, 0) + if !success { + return nil, fmt.Errorf("failed to convert %s to big.Int", symbol) + } + return id, nil +} diff --git a/services/wallet/common/helpers_test.go b/services/wallet/common/helpers_test.go new file mode 100644 index 00000000000..05f5cdeb2b7 --- /dev/null +++ b/services/wallet/common/helpers_test.go @@ -0,0 +1,30 @@ +package common + +import ( + "encoding/hex" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + + "github.com/stretchr/testify/require" +) + +func TestPackApprovalInputData(t *testing.T) { + + expectedData := "095ea7b3000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000064" + + addr := common.HexToAddress("0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa") + data, err := PackApprovalInputData(big.NewInt(100), &addr) + require.NoError(t, err) + require.Equal(t, expectedData, hex.EncodeToString(data)) +} + +func TestGetTokenIdFromSymbol(t *testing.T) { + + expectedData := big.NewInt(100) + + data, err := GetTokenIdFromSymbol(expectedData.String()) + require.NoError(t, err) + require.Equal(t, expectedData, data) +} diff --git a/services/wallet/router/pathprocessor/processor_erc1155.go b/services/wallet/router/pathprocessor/processor_erc1155.go index 1f8b7d43c73..4ccc795d47a 100644 --- a/services/wallet/router/pathprocessor/processor_erc1155.go +++ b/services/wallet/router/pathprocessor/processor_erc1155.go @@ -2,7 +2,6 @@ package pathprocessor import ( "context" - "fmt" "math/big" "strings" @@ -58,9 +57,9 @@ func (s *ERC1155Processor) PackTxInputData(params ProcessorInputParams) ([]byte, return []byte{}, createERC1155ErrorResponse(err) } - id, success := big.NewInt(0).SetString(params.FromToken.Symbol, 0) - if !success { - return []byte{}, createERC1155ErrorResponse(fmt.Errorf("failed to convert %s to big.Int", params.FromToken.Symbol)) + id, err := walletCommon.GetTokenIdFromSymbol(params.FromToken.Symbol) + if err != nil { + return []byte{}, createERC1155ErrorResponse(err) } return abi.Pack("safeTransferFrom", diff --git a/services/wallet/router/pathprocessor/processor_erc721.go b/services/wallet/router/pathprocessor/processor_erc721.go index 5591596bba8..eeb4bf38e75 100644 --- a/services/wallet/router/pathprocessor/processor_erc721.go +++ b/services/wallet/router/pathprocessor/processor_erc721.go @@ -2,7 +2,6 @@ package pathprocessor import ( "context" - "fmt" "math/big" "strings" @@ -65,9 +64,9 @@ func (s *ERC721Processor) packTxInputDataInternally(params ProcessorInputParams, return []byte{}, createERC721ErrorResponse(err) } - id, success := big.NewInt(0).SetString(params.FromToken.Symbol, 0) - if !success { - return []byte{}, createERC721ErrorResponse(fmt.Errorf("failed to convert %s to big.Int", params.FromToken.Symbol)) + id, err := walletCommon.GetTokenIdFromSymbol(params.FromToken.Symbol) + if err != nil { + return []byte{}, createERC721ErrorResponse(err) } return abi.Pack(functionName, diff --git a/services/wallet/router/router_helper.go b/services/wallet/router/router_helper.go index ee1ae45565a..2468602de6a 100644 --- a/services/wallet/router/router_helper.go +++ b/services/wallet/router/router_helper.go @@ -4,16 +4,13 @@ import ( "context" "errors" "math/big" - "strings" "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/status-im/status-go/contracts" gaspriceoracle "github.com/status-im/status-go/contracts/gas-price-oracle" - "github.com/status-im/status-go/contracts/ierc20" "github.com/status-im/status-go/params" "github.com/status-im/status-go/services/wallet/bigint" walletCommon "github.com/status-im/status-go/services/wallet/common" @@ -67,21 +64,8 @@ func (r *Router) requireApproval(ctx context.Context, sendType sendtype.SendType return true, params.AmountIn, nil } -func (r *Router) packApprovalInputData(amountIn *big.Int, approvalContractAddress *common.Address) ([]byte, error) { - if approvalContractAddress == nil || *approvalContractAddress == walletCommon.ZeroAddress { - return []byte{}, nil - } - - erc20ABI, err := abi.JSON(strings.NewReader(ierc20.IERC20ABI)) - if err != nil { - return []byte{}, err - } - - return erc20ABI.Pack("approve", approvalContractAddress, amountIn) -} - func (r *Router) estimateGasForApproval(params pathprocessor.ProcessorInputParams, approvalContractAddress *common.Address) (uint64, error) { - data, err := r.packApprovalInputData(params.AmountIn, approvalContractAddress) + data, err := walletCommon.PackApprovalInputData(params.AmountIn, approvalContractAddress) if err != nil { return 0, err } @@ -100,7 +84,7 @@ func (r *Router) estimateGasForApproval(params pathprocessor.ProcessorInputParam } func (r *Router) calculateApprovalL1Fee(amountIn *big.Int, chainID uint64, approvalContractAddress *common.Address) (uint64, error) { - data, err := r.packApprovalInputData(amountIn, approvalContractAddress) + data, err := walletCommon.PackApprovalInputData(amountIn, approvalContractAddress) if err != nil { return 0, err } From bbdab48b3464d074cc2fb8e192c60293433ecd2d Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Mon, 23 Sep 2024 09:15:24 +0200 Subject: [PATCH 3/6] feat_: send tx args type extended with new properties Added props: - `Version` used to differ old and new usage - `ValueOut` - `FromChainID` - `ToChainID` - `FromTokenID` - `ToTokenID` - `ToContractAddress` - `SlippagePercentage` --- transactions/types.go | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/transactions/types.go b/transactions/types.go index cab661892db..12f1b300bc9 100644 --- a/transactions/types.go +++ b/transactions/types.go @@ -15,6 +15,13 @@ import ( wallet_common "github.com/status-im/status-go/services/wallet/common" ) +type SendTxArgsVersion uint + +const ( + SendTxArgsVersion0 SendTxArgsVersion = 0 + SendTxArgsVersion1 SendTxArgsVersion = 1 +) + var ( // ErrInvalidSendTxArgs is returned when the structure of SendTxArgs is ambigious. ErrInvalidSendTxArgs = errors.New("transaction arguments are invalid") @@ -41,6 +48,8 @@ type GasCalculator interface { // This struct is based on go-ethereum's type in internal/ethapi/api.go, but we have freedom // over the exact layout of this struct. type SendTxArgs struct { + Version SendTxArgsVersion `json:"version"` + From types.Address `json:"from"` To *types.Address `json:"to"` Gas *hexutil.Uint64 `json:"gas"` @@ -55,9 +64,17 @@ type SendTxArgs struct { Input types.HexBytes `json:"input"` Data types.HexBytes `json:"data"` - // additional data - MultiTransactionID wallet_common.MultiTransactionIDType - Symbol string + // additional data - version SendTxArgsVersion0 + MultiTransactionID wallet_common.MultiTransactionIDType `json:"multiTransactionID"` + Symbol string `json:"-"` + // additional data - version SendTxArgsVersion1 + ValueOut *hexutil.Big `json:"-"` + FromChainID uint64 `json:"-"` + ToChainID uint64 `json:"-"` + FromTokenID string `json:"-"` + ToTokenID string `json:"-"` + ToContractAddress types.Address `json:"-"` // represents address of the contract that needs to be used in order to send assets, like ERC721 or ERC1155 tx + SlippagePercentage float32 `json:"-"` } // Valid checks whether this structure is filled in correctly. From afc982bd271663c56735b7931606af2276c7501d Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Mon, 23 Sep 2024 09:16:03 +0200 Subject: [PATCH 4/6] chore_: build transaction v2 function added to processors --- .../mock_pathprocessor/processor.go | 41 ++++++---- .../wallet/router/pathprocessor/processor.go | 3 + .../pathprocessor/processor_bridge_celar.go | 68 ++++++++++++++++ .../pathprocessor/processor_bridge_hop.go | 73 +++++++++++++++++ .../pathprocessor/processor_ens_public_key.go | 4 + .../pathprocessor/processor_ens_register.go | 4 + .../pathprocessor/processor_ens_release.go | 4 + .../router/pathprocessor/processor_erc1155.go | 54 +++++++++++++ .../router/pathprocessor/processor_erc721.go | 79 ++++++++++++++++++- .../pathprocessor/processor_stickers_buy.go | 4 + .../pathprocessor/processor_swap_paraswap.go | 54 +++++++++++++ .../pathprocessor/processor_transfer.go | 6 +- 12 files changed, 377 insertions(+), 17 deletions(-) diff --git a/services/wallet/router/pathprocessor/mock_pathprocessor/processor.go b/services/wallet/router/pathprocessor/mock_pathprocessor/processor.go index 25a9e3ab9e9..d0ffdc961f9 100644 --- a/services/wallet/router/pathprocessor/mock_pathprocessor/processor.go +++ b/services/wallet/router/pathprocessor/mock_pathprocessor/processor.go @@ -1,10 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. // Source: services/wallet/router/pathprocessor/processor.go -// -// Generated by this command: -// -// mockgen -package=mock_pathprocessor -destination=services/wallet/router/pathprocessor/mock_pathprocessor/processor.go -source=services/wallet/router/pathprocessor/processor.go -// // Package mock_pathprocessor is a generated GoMock package. package mock_pathprocessor @@ -13,13 +8,13 @@ import ( big "math/big" reflect "reflect" - gomock "go.uber.org/mock/gomock" - common "github.com/ethereum/go-ethereum/common" types "github.com/ethereum/go-ethereum/core/types" + gomock "go.uber.org/mock/gomock" account "github.com/status-im/status-go/account" types0 "github.com/status-im/status-go/eth-node/types" pathprocessor "github.com/status-im/status-go/services/wallet/router/pathprocessor" + transactions "github.com/status-im/status-go/transactions" ) // MockPathProcessor is a mock of PathProcessor interface. @@ -55,7 +50,7 @@ func (m *MockPathProcessor) AvailableFor(params pathprocessor.ProcessorInputPara } // AvailableFor indicates an expected call of AvailableFor. -func (mr *MockPathProcessorMockRecorder) AvailableFor(params any) *gomock.Call { +func (mr *MockPathProcessorMockRecorder) AvailableFor(params interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AvailableFor", reflect.TypeOf((*MockPathProcessor)(nil).AvailableFor), params) } @@ -71,11 +66,27 @@ func (m *MockPathProcessor) BuildTransaction(sendArgs *pathprocessor.MultipathPr } // BuildTransaction indicates an expected call of BuildTransaction. -func (mr *MockPathProcessorMockRecorder) BuildTransaction(sendArgs, lastUsedNonce any) *gomock.Call { +func (mr *MockPathProcessorMockRecorder) BuildTransaction(sendArgs, lastUsedNonce interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildTransaction", reflect.TypeOf((*MockPathProcessor)(nil).BuildTransaction), sendArgs, lastUsedNonce) } +// BuildTransactionV2 mocks base method. +func (m *MockPathProcessor) BuildTransactionV2(sendArgs *transactions.SendTxArgs, lastUsedNonce int64) (*types.Transaction, uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BuildTransactionV2", sendArgs, lastUsedNonce) + ret0, _ := ret[0].(*types.Transaction) + ret1, _ := ret[1].(uint64) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// BuildTransactionV2 indicates an expected call of BuildTransactionV2. +func (mr *MockPathProcessorMockRecorder) BuildTransactionV2(sendArgs, lastUsedNonce interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildTransactionV2", reflect.TypeOf((*MockPathProcessor)(nil).BuildTransactionV2), sendArgs, lastUsedNonce) +} + // CalculateAmountOut mocks base method. func (m *MockPathProcessor) CalculateAmountOut(params pathprocessor.ProcessorInputParams) (*big.Int, error) { m.ctrl.T.Helper() @@ -86,7 +97,7 @@ func (m *MockPathProcessor) CalculateAmountOut(params pathprocessor.ProcessorInp } // CalculateAmountOut indicates an expected call of CalculateAmountOut. -func (mr *MockPathProcessorMockRecorder) CalculateAmountOut(params any) *gomock.Call { +func (mr *MockPathProcessorMockRecorder) CalculateAmountOut(params interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CalculateAmountOut", reflect.TypeOf((*MockPathProcessor)(nil).CalculateAmountOut), params) } @@ -102,7 +113,7 @@ func (m *MockPathProcessor) CalculateFees(params pathprocessor.ProcessorInputPar } // CalculateFees indicates an expected call of CalculateFees. -func (mr *MockPathProcessorMockRecorder) CalculateFees(params any) *gomock.Call { +func (mr *MockPathProcessorMockRecorder) CalculateFees(params interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CalculateFees", reflect.TypeOf((*MockPathProcessor)(nil).CalculateFees), params) } @@ -117,7 +128,7 @@ func (m *MockPathProcessor) EstimateGas(params pathprocessor.ProcessorInputParam } // EstimateGas indicates an expected call of EstimateGas. -func (mr *MockPathProcessorMockRecorder) EstimateGas(params any) *gomock.Call { +func (mr *MockPathProcessorMockRecorder) EstimateGas(params interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EstimateGas", reflect.TypeOf((*MockPathProcessor)(nil).EstimateGas), params) } @@ -132,7 +143,7 @@ func (m *MockPathProcessor) GetContractAddress(params pathprocessor.ProcessorInp } // GetContractAddress indicates an expected call of GetContractAddress. -func (mr *MockPathProcessorMockRecorder) GetContractAddress(params any) *gomock.Call { +func (mr *MockPathProcessorMockRecorder) GetContractAddress(params interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContractAddress", reflect.TypeOf((*MockPathProcessor)(nil).GetContractAddress), params) } @@ -161,7 +172,7 @@ func (m *MockPathProcessor) PackTxInputData(params pathprocessor.ProcessorInputP } // PackTxInputData indicates an expected call of PackTxInputData. -func (mr *MockPathProcessorMockRecorder) PackTxInputData(params any) *gomock.Call { +func (mr *MockPathProcessorMockRecorder) PackTxInputData(params interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PackTxInputData", reflect.TypeOf((*MockPathProcessor)(nil).PackTxInputData), params) } @@ -177,7 +188,7 @@ func (m *MockPathProcessor) Send(sendArgs *pathprocessor.MultipathProcessorTxArg } // Send indicates an expected call of Send. -func (mr *MockPathProcessorMockRecorder) Send(sendArgs, lastUsedNonce, verifiedAccount any) *gomock.Call { +func (mr *MockPathProcessorMockRecorder) Send(sendArgs, lastUsedNonce, verifiedAccount interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockPathProcessor)(nil).Send), sendArgs, lastUsedNonce, verifiedAccount) } diff --git a/services/wallet/router/pathprocessor/processor.go b/services/wallet/router/pathprocessor/processor.go index b9e539f9118..0b5275daf45 100644 --- a/services/wallet/router/pathprocessor/processor.go +++ b/services/wallet/router/pathprocessor/processor.go @@ -10,6 +10,7 @@ import ( "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/params" "github.com/status-im/status-go/services/wallet/token" + "github.com/status-im/status-go/transactions" ) type PathProcessor interface { @@ -31,6 +32,8 @@ type PathProcessor interface { GetContractAddress(params ProcessorInputParams) (common.Address, error) // BuildTransaction builds the transaction based on MultipathProcessorTxArgs, returns the transaction and the used nonce (lastUsedNonce is -1 if it's the first tx) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) + // BuildTransactionV2 builds the transaction based on SendTxArgs, returns the transaction and the used nonce (lastUsedNonce is -1 if it's the first tx) + BuildTransactionV2(sendArgs *transactions.SendTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) } type PathProcessorClearable interface { diff --git a/services/wallet/router/pathprocessor/processor_bridge_celar.go b/services/wallet/router/pathprocessor/processor_bridge_celar.go index 3e187f07f86..5811c26e503 100644 --- a/services/wallet/router/pathprocessor/processor_bridge_celar.go +++ b/services/wallet/router/pathprocessor/processor_bridge_celar.go @@ -302,6 +302,7 @@ func (s *CelerBridgeProcessor) GetContractAddress(params ProcessorInputParams) ( return common.Address{}, ErrContractNotFound } +// TODO: remove this struct once mobile switches to the new approach func (s *CelerBridgeProcessor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signerFn bind.SignerFn, lastUsedNonce int64) (*ethTypes.Transaction, error) { fromChain := s.rpcClient.NetworkManager.Find(sendArgs.ChainID) if fromChain == nil { @@ -364,6 +365,68 @@ func (s *CelerBridgeProcessor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, s return tx, nil } +func (s *CelerBridgeProcessor) sendOrBuildV2(sendArgs *transactions.SendTxArgs, signerFn bind.SignerFn, lastUsedNonce int64) (*ethTypes.Transaction, error) { + fromChain := s.rpcClient.NetworkManager.Find(sendArgs.FromChainID) + if fromChain == nil { + return nil, ErrNetworkNotFound + } + token := s.tokenManager.FindToken(fromChain, sendArgs.FromTokenID) + if token == nil { + return nil, ErrTokenNotFound + } + addrs, err := s.GetContractAddress(ProcessorInputParams{ + FromChain: fromChain, + }) + if err != nil { + return nil, createBridgeCellerErrorResponse(err) + } + + backend, err := s.rpcClient.EthClient(sendArgs.FromChainID) + if err != nil { + return nil, createBridgeCellerErrorResponse(err) + } + contract, err := celer.NewCeler(addrs, backend) + if err != nil { + return nil, createBridgeCellerErrorResponse(err) + } + + if lastUsedNonce >= 0 { + lastUsedNonceHexUtil := hexutil.Uint64(uint64(lastUsedNonce) + 1) + sendArgs.Nonce = &lastUsedNonceHexUtil + } + + var tx *ethTypes.Transaction + txOpts := sendArgs.ToTransactOpts(signerFn) + if token.IsNative() { + tx, err = contract.SendNative( + txOpts, + common.Address(*sendArgs.To), + (*big.Int)(sendArgs.Value), + sendArgs.FromChainID, + uint64(time.Now().UnixMilli()), + maxSlippage, + ) + } else { + tx, err = contract.Send( + txOpts, + common.Address(*sendArgs.To), + token.Address, + (*big.Int)(sendArgs.Value), + sendArgs.FromChainID, + uint64(time.Now().UnixMilli()), + maxSlippage, + ) + } + if err != nil { + return tx, createBridgeCellerErrorResponse(err) + } + err = s.transactor.StoreAndTrackPendingTx(txOpts.From, sendArgs.FromTokenID, sendArgs.FromChainID, sendArgs.MultiTransactionID, tx) + if err != nil { + return tx, createBridgeCellerErrorResponse(err) + } + return tx, nil +} + func (s *CelerBridgeProcessor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (types.Hash, uint64, error) { tx, err := s.sendOrBuild(sendArgs, getSigner(sendArgs.ChainID, sendArgs.CbridgeTx.From, verifiedAccount), lastUsedNonce) if err != nil { @@ -378,6 +441,11 @@ func (s *CelerBridgeProcessor) BuildTransaction(sendArgs *MultipathProcessorTxAr return tx, tx.Nonce(), err } +func (s *CelerBridgeProcessor) BuildTransactionV2(sendArgs *transactions.SendTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + tx, err := s.sendOrBuildV2(sendArgs, nil, lastUsedNonce) + return tx, tx.Nonce(), err +} + func (s *CelerBridgeProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { amt, err := s.estimateAmt(params.FromChain, params.ToChain, params.AmountIn, params.FromToken.Symbol) if err != nil { diff --git a/services/wallet/router/pathprocessor/processor_bridge_hop.go b/services/wallet/router/pathprocessor/processor_bridge_hop.go index 6753d450af7..1273294d13a 100644 --- a/services/wallet/router/pathprocessor/processor_bridge_hop.go +++ b/services/wallet/router/pathprocessor/processor_bridge_hop.go @@ -282,6 +282,7 @@ func (h *HopBridgeProcessor) GetContractAddress(params ProcessorInputParams) (co return address, createBridgeHopErrorResponse(err) } +// TODO: remove this struct once mobile switches to the new approach func (h *HopBridgeProcessor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signerFn bind.SignerFn, lastUsedNonce int64) (tx *ethTypes.Transaction, err error) { fromChain := h.networkManager.Find(sendArgs.HopTx.ChainID) if fromChain == nil { @@ -349,6 +350,73 @@ func (h *HopBridgeProcessor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, sig return tx, nil } +func (h *HopBridgeProcessor) sendOrBuildV2(sendArgs *transactions.SendTxArgs, signerFn bind.SignerFn, lastUsedNonce int64) (tx *ethTypes.Transaction, err error) { + fromChain := h.networkManager.Find(sendArgs.FromChainID) + if fromChain == nil { + return tx, fmt.Errorf("ChainID not supported %d", sendArgs.FromChainID) + } + + token := h.tokenManager.FindToken(fromChain, sendArgs.FromTokenID) + + var nonce uint64 + if lastUsedNonce < 0 { + nonce, err = h.transactor.NextNonce(h.contractMaker.RPCClient, fromChain.ChainID, sendArgs.From) + if err != nil { + return tx, createBridgeHopErrorResponse(err) + } + } else { + nonce = uint64(lastUsedNonce) + 1 + } + + argNonce := hexutil.Uint64(nonce) + sendArgs.Nonce = &argNonce + + txOpts := sendArgs.ToTransactOpts(signerFn) + if token.IsNative() { + txOpts.Value = (*big.Int)(sendArgs.Value) + } + + ethClient, err := h.contractMaker.RPCClient.EthClient(fromChain.ChainID) + if err != nil { + return tx, createBridgeHopErrorResponse(err) + } + + contractAddress, contractType, err := hop.GetContractAddress(fromChain.ChainID, sendArgs.FromTokenID) + if err != nil { + return tx, createBridgeHopErrorResponse(err) + } + + bonderKey := makeKey(sendArgs.FromChainID, sendArgs.ToChainID, "", "") + bonderFeeIns, ok := h.bonderFee.Load(bonderKey) + if !ok { + return nil, ErrNoBonderFeeFound + } + bonderFee := bonderFeeIns.(*BonderFee) + + switch contractType { + case hop.CctpL1Bridge: + tx, err = h.sendCctpL1BridgeTx(contractAddress, ethClient, sendArgs.ToChainID, common.Address(*sendArgs.To), txOpts, bonderFee) + case hop.L1Bridge: + tx, err = h.sendL1BridgeTx(contractAddress, ethClient, sendArgs.ToChainID, common.Address(*sendArgs.To), txOpts, token, bonderFee) + case hop.L2AmmWrapper: + tx, err = h.sendL2AmmWrapperTx(contractAddress, ethClient, sendArgs.ToChainID, common.Address(*sendArgs.To), txOpts, bonderFee) + case hop.CctpL2Bridge: + tx, err = h.sendCctpL2BridgeTx(contractAddress, ethClient, sendArgs.ToChainID, common.Address(*sendArgs.To), txOpts, bonderFee) + case hop.L2Bridge: + tx, err = h.sendL2BridgeTx(contractAddress, ethClient, sendArgs.ToChainID, common.Address(*sendArgs.To), txOpts, bonderFee) + default: + return tx, ErrContractTypeNotSupported + } + if err != nil { + return tx, createBridgeHopErrorResponse(err) + } + err = h.transactor.StoreAndTrackPendingTx(txOpts.From, sendArgs.FromTokenID, sendArgs.FromChainID, sendArgs.MultiTransactionID, tx) + if err != nil { + return tx, createBridgeHopErrorResponse(err) + } + return tx, nil +} + func (h *HopBridgeProcessor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, nonce uint64, err error) { tx, err := h.sendOrBuild(sendArgs, getSigner(sendArgs.HopTx.ChainID, sendArgs.HopTx.From, verifiedAccount), lastUsedNonce) if err != nil { @@ -362,6 +430,11 @@ func (h *HopBridgeProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs return tx, tx.Nonce(), createBridgeHopErrorResponse(err) } +func (h *HopBridgeProcessor) BuildTransactionV2(sendArgs *transactions.SendTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + tx, err := h.sendOrBuildV2(sendArgs, nil, lastUsedNonce) + return tx, tx.Nonce(), createBridgeHopErrorResponse(err) +} + func (h *HopBridgeProcessor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) { bonderKey := makeKey(params.FromChain.ChainID, params.ToChain.ChainID, "", "") if params.TestsMode { diff --git a/services/wallet/router/pathprocessor/processor_ens_public_key.go b/services/wallet/router/pathprocessor/processor_ens_public_key.go index b0134439cc1..72b47a3ed36 100644 --- a/services/wallet/router/pathprocessor/processor_ens_public_key.go +++ b/services/wallet/router/pathprocessor/processor_ens_public_key.go @@ -111,6 +111,10 @@ func (s *ENSPublicKeyProcessor) BuildTransaction(sendArgs *MultipathProcessorTxA return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce) } +func (s *ENSPublicKeyProcessor) BuildTransactionV2(sendArgs *transactions.SendTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.FromChainID, *sendArgs, lastUsedNonce) +} + func (s *ENSPublicKeyProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { return params.AmountIn, nil } diff --git a/services/wallet/router/pathprocessor/processor_ens_register.go b/services/wallet/router/pathprocessor/processor_ens_register.go index fef94839d6c..895ca753a4f 100644 --- a/services/wallet/router/pathprocessor/processor_ens_register.go +++ b/services/wallet/router/pathprocessor/processor_ens_register.go @@ -147,6 +147,10 @@ func (s *ENSRegisterProcessor) BuildTransaction(sendArgs *MultipathProcessorTxAr return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce) } +func (s *ENSRegisterProcessor) BuildTransactionV2(sendArgs *transactions.SendTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.FromChainID, *sendArgs, lastUsedNonce) +} + func (s *ENSRegisterProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { return params.AmountIn, nil } diff --git a/services/wallet/router/pathprocessor/processor_ens_release.go b/services/wallet/router/pathprocessor/processor_ens_release.go index 6f0d56aa2dc..e269e7a2970 100644 --- a/services/wallet/router/pathprocessor/processor_ens_release.go +++ b/services/wallet/router/pathprocessor/processor_ens_release.go @@ -110,6 +110,10 @@ func (s *ENSReleaseProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArg return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce) } +func (s *ENSReleaseProcessor) BuildTransactionV2(sendArgs *transactions.SendTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.FromChainID, *sendArgs, lastUsedNonce) +} + func (s *ENSReleaseProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { return params.AmountIn, nil } diff --git a/services/wallet/router/pathprocessor/processor_erc1155.go b/services/wallet/router/pathprocessor/processor_erc1155.go index 4ccc795d47a..9d3e06326d1 100644 --- a/services/wallet/router/pathprocessor/processor_erc1155.go +++ b/services/wallet/router/pathprocessor/processor_erc1155.go @@ -108,6 +108,7 @@ func (s *ERC1155Processor) EstimateGas(params ProcessorInputParams) (uint64, err return uint64(increasedEstimation), nil } +// TODO: remove this struct once mobile switches to the new approach func (s *ERC1155Processor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signerFn bind.SignerFn, lastUsedNonce int64) (tx *ethTypes.Transaction, err error) { ethClient, err := s.rpcClient.EthClient(sendArgs.ChainID) if err != nil { @@ -150,6 +151,54 @@ func (s *ERC1155Processor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signe return tx, nil } +func (s *ERC1155Processor) sendOrBuildV2(sendArgs *transactions.SendTxArgs, signerFn bind.SignerFn, lastUsedNonce int64) (tx *ethTypes.Transaction, err error) { + ethClient, err := s.rpcClient.EthClient(sendArgs.FromChainID) + if err != nil { + return tx, createERC1155ErrorResponse(err) + } + + contract, err := ierc1155.NewIerc1155(common.Address(sendArgs.ToContractAddress), ethClient) + if err != nil { + return tx, createERC1155ErrorResponse(err) + } + + id, err := walletCommon.GetTokenIdFromSymbol(sendArgs.FromTokenID) + if err != nil { + return tx, createERC1155ErrorResponse(err) + } + + var nonce uint64 + if lastUsedNonce < 0 { + nonce, err = s.transactor.NextNonce(s.rpcClient, sendArgs.FromChainID, sendArgs.From) + if err != nil { + return tx, createERC1155ErrorResponse(err) + } + } else { + nonce = uint64(lastUsedNonce) + 1 + } + + argNonce := hexutil.Uint64(nonce) + sendArgs.Nonce = &argNonce + txOpts := sendArgs.ToTransactOpts(signerFn) + from := common.Address(sendArgs.From) + tx, err = contract.SafeTransferFrom( + txOpts, + from, + common.Address(*sendArgs.To), + id, + sendArgs.Value.ToInt(), + []byte{}, + ) + if err != nil { + return tx, createERC1155ErrorResponse(err) + } + err = s.transactor.StoreAndTrackPendingTx(from, sendArgs.FromTokenID, sendArgs.FromChainID, sendArgs.MultiTransactionID, tx) + if err != nil { + return tx, createERC1155ErrorResponse(err) + } + return tx, nil +} + func (s *ERC1155Processor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, usedNonce uint64, err error) { tx, err := s.sendOrBuild(sendArgs, getSigner(sendArgs.ChainID, sendArgs.ERC1155TransferTx.From, verifiedAccount), lastUsedNonce) if err != nil { @@ -163,6 +212,11 @@ func (s *ERC1155Processor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, return tx, tx.Nonce(), err } +func (s *ERC1155Processor) BuildTransactionV2(sendArgs *transactions.SendTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + tx, err := s.sendOrBuildV2(sendArgs, nil, lastUsedNonce) + return tx, tx.Nonce(), err +} + func (s *ERC1155Processor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { return params.AmountIn, nil } diff --git a/services/wallet/router/pathprocessor/processor_erc721.go b/services/wallet/router/pathprocessor/processor_erc721.go index eeb4bf38e75..d4124fc28c6 100644 --- a/services/wallet/router/pathprocessor/processor_erc721.go +++ b/services/wallet/router/pathprocessor/processor_erc721.go @@ -146,6 +146,7 @@ func (s *ERC721Processor) EstimateGas(params ProcessorInputParams) (uint64, erro return uint64(increasedEstimation), nil } +// TODO: remove this struct once mobile switches to the new approach func (s *ERC721Processor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signerFn bind.SignerFn, lastUsedNonce int64) (tx *ethTypes.Transaction, err error) { from := common.Address(sendArgs.ERC721TransferTx.From) @@ -157,7 +158,8 @@ func (s *ERC721Processor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signer FromAddr: from, ToAddr: sendArgs.ERC721TransferTx.Recipient, FromToken: &token.Token{ - Symbol: sendArgs.ERC721TransferTx.TokenID.String(), + Symbol: sendArgs.ERC721TransferTx.TokenID.String(), + Address: common.Address(*sendArgs.ERC721TransferTx.To), }, } err = s.checkIfFunctionExists(inputParams, functionNameSafeTransferFrom) @@ -207,6 +209,76 @@ func (s *ERC721Processor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signer return tx, nil } +func (s *ERC721Processor) sendOrBuildV2(sendArgs *transactions.SendTxArgs, signerFn bind.SignerFn, lastUsedNonce int64) (tx *ethTypes.Transaction, err error) { + from := common.Address(sendArgs.From) + to := common.Address(*sendArgs.To) + + useSafeTransferFrom := true + inputParams := ProcessorInputParams{ + FromChain: ¶ms.Network{ + ChainID: sendArgs.FromChainID, + }, + FromAddr: from, + ToAddr: to, + FromToken: &token.Token{ + Symbol: sendArgs.FromTokenID, + Address: common.Address(sendArgs.ToContractAddress), + }, + } + err = s.checkIfFunctionExists(inputParams, functionNameSafeTransferFrom) + if err != nil { + useSafeTransferFrom = false + } + + ethClient, err := s.rpcClient.EthClient(sendArgs.FromChainID) + if err != nil { + return tx, createERC721ErrorResponse(err) + } + + contract, err := collectibles.NewCollectibles(common.Address(sendArgs.ToContractAddress), ethClient) + if err != nil { + return tx, createERC721ErrorResponse(err) + } + + id, err := walletCommon.GetTokenIdFromSymbol(sendArgs.FromTokenID) + if err != nil { + return tx, createERC1155ErrorResponse(err) + } + + var nonce uint64 + if lastUsedNonce < 0 { + nonce, err = s.transactor.NextNonce(s.rpcClient, sendArgs.FromChainID, sendArgs.From) + if err != nil { + return tx, createERC721ErrorResponse(err) + } + } else { + nonce = uint64(lastUsedNonce) + 1 + } + + argNonce := hexutil.Uint64(nonce) + sendArgs.Nonce = &argNonce + txOpts := sendArgs.ToTransactOpts(signerFn) + if useSafeTransferFrom { + tx, err = contract.SafeTransferFrom(txOpts, + from, + to, + id) + } else { + tx, err = contract.TransferFrom(txOpts, + from, + to, + id) + } + if err != nil { + return tx, createERC721ErrorResponse(err) + } + err = s.transactor.StoreAndTrackPendingTx(from, sendArgs.FromTokenID, sendArgs.FromChainID, sendArgs.MultiTransactionID, tx) + if err != nil { + return tx, createERC721ErrorResponse(err) + } + return tx, nil +} + func (s *ERC721Processor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, usedNonce uint64, err error) { tx, err := s.sendOrBuild(sendArgs, getSigner(sendArgs.ChainID, sendArgs.ERC721TransferTx.From, verifiedAccount), lastUsedNonce) if err != nil { @@ -220,6 +292,11 @@ func (s *ERC721Processor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, l return tx, tx.Nonce(), err } +func (s *ERC721Processor) BuildTransactionV2(sendArgs *transactions.SendTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + tx, err := s.sendOrBuildV2(sendArgs, nil, lastUsedNonce) + return tx, tx.Nonce(), err +} + func (s *ERC721Processor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { return params.AmountIn, nil } diff --git a/services/wallet/router/pathprocessor/processor_stickers_buy.go b/services/wallet/router/pathprocessor/processor_stickers_buy.go index 8e83dbd1af0..d723e33b2de 100644 --- a/services/wallet/router/pathprocessor/processor_stickers_buy.go +++ b/services/wallet/router/pathprocessor/processor_stickers_buy.go @@ -139,6 +139,10 @@ func (s *StickersBuyProcessor) BuildTransaction(sendArgs *MultipathProcessorTxAr return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce) } +func (s *StickersBuyProcessor) BuildTransactionV2(sendArgs *transactions.SendTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.FromChainID, *sendArgs, lastUsedNonce) +} + func (s *StickersBuyProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { return params.AmountIn, nil } diff --git a/services/wallet/router/pathprocessor/processor_swap_paraswap.go b/services/wallet/router/pathprocessor/processor_swap_paraswap.go index 991c2a59183..21d6050de35 100644 --- a/services/wallet/router/pathprocessor/processor_swap_paraswap.go +++ b/services/wallet/router/pathprocessor/processor_swap_paraswap.go @@ -208,6 +208,7 @@ func (s *SwapParaswapProcessor) GetContractAddress(params ProcessorInputParams) return priceRoute.TokenTransferProxy, nil } +// TODO: remove this struct once mobile switches to the new approach func (s *SwapParaswapProcessor) prepareTransaction(sendArgs *MultipathProcessorTxArgs) error { slippageBP := uint(sendArgs.SwapTx.SlippagePercentage * 100) // convert to basis points @@ -254,6 +255,51 @@ func (s *SwapParaswapProcessor) prepareTransaction(sendArgs *MultipathProcessorT return nil } +func (s *SwapParaswapProcessor) prepareTransactionV2(sendArgs *transactions.SendTxArgs) error { + slippageBP := uint(sendArgs.SlippagePercentage * 100) // convert to basis points + + key := makeKey(sendArgs.FromChainID, sendArgs.ToChainID, sendArgs.FromTokenID, sendArgs.ToTokenID) + priceRouteIns, ok := s.priceRoute.Load(key) + if !ok { + return ErrPriceRouteNotFound + } + priceRoute := priceRouteIns.(*paraswap.Route) + + tx, err := s.paraswapClient.BuildTransaction(context.Background(), priceRoute.SrcTokenAddress, priceRoute.SrcTokenDecimals, priceRoute.SrcAmount.Int, + priceRoute.DestTokenAddress, priceRoute.DestTokenDecimals, priceRoute.DestAmount.Int, slippageBP, + common.Address(sendArgs.From), common.Address(*sendArgs.To), + priceRoute.RawPriceRoute, priceRoute.Side) + if err != nil { + return createSwapParaswapErrorResponse(err) + } + + value, ok := new(big.Int).SetString(tx.Value, 10) + if !ok { + return ErrConvertingAmountToBigInt + } + + gas, err := strconv.ParseUint(tx.Gas, 10, 64) + if err != nil { + return createSwapParaswapErrorResponse(err) + } + + gasPrice, ok := new(big.Int).SetString(tx.GasPrice, 10) + if !ok { + return ErrConvertingAmountToBigInt + } + + sendArgs.FromChainID = tx.ChainID + toAddr := types.HexToAddress(tx.To) + sendArgs.From = types.HexToAddress(tx.From) + sendArgs.To = &toAddr + sendArgs.Value = (*hexutil.Big)(value) + sendArgs.Gas = (*hexutil.Uint64)(&gas) + sendArgs.GasPrice = (*hexutil.Big)(gasPrice) + sendArgs.Data = types.Hex2Bytes(tx.Data) + + return nil +} + func (s *SwapParaswapProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { err := s.prepareTransaction(sendArgs) if err != nil { @@ -262,6 +308,14 @@ func (s *SwapParaswapProcessor) BuildTransaction(sendArgs *MultipathProcessorTxA return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, sendArgs.SwapTx.SendTxArgs, lastUsedNonce) } +func (s *SwapParaswapProcessor) BuildTransactionV2(sendArgs *transactions.SendTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + err := s.prepareTransactionV2(sendArgs) + if err != nil { + return nil, 0, createSwapParaswapErrorResponse(err) + } + return s.transactor.ValidateAndBuildTransaction(sendArgs.FromChainID, *sendArgs, lastUsedNonce) +} + func (s *SwapParaswapProcessor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (types.Hash, uint64, error) { err := s.prepareTransaction(sendArgs) if err != nil { diff --git a/services/wallet/router/pathprocessor/processor_transfer.go b/services/wallet/router/pathprocessor/processor_transfer.go index 4fa056eb4de..7e1e3faa617 100644 --- a/services/wallet/router/pathprocessor/processor_transfer.go +++ b/services/wallet/router/pathprocessor/processor_transfer.go @@ -53,7 +53,7 @@ func (s *TransferProcessor) CalculateFees(params ProcessorInputParams) (*big.Int func (s *TransferProcessor) PackTxInputData(params ProcessorInputParams) ([]byte, error) { if params.FromToken.IsNative() { - return []byte("eth_sendRawTransaction"), nil + return []byte{}, nil } else { abi, err := abi.JSON(strings.NewReader(ierc20.IERC20ABI)) if err != nil { @@ -122,6 +122,10 @@ func (s *TransferProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce) } +func (s *TransferProcessor) BuildTransactionV2(sendArgs *transactions.SendTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.FromChainID, *sendArgs, lastUsedNonce) +} + func (s *TransferProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { return params.AmountIn, nil } From 5ad830c831fdecebb84925175cf2e4395d4cf32f Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Mon, 23 Sep 2024 09:19:00 +0200 Subject: [PATCH 5/6] chore_: improvements of the sending route generated by the router process This commit simplifies the sending process of the best route suggested by the router. It also makes the sending process the same for accounts (key pairs) migrated to a keycard and those stored locally in local keystore files. Deprecated endpoints: - `CreateMultiTransaction` - `ProceedWithTransactionsSignatures` Deprecated signal: - `wallet.sign.transactions` New endpoints: - `BuildTransactionsFromRoute` - `SendRouterTransactionsWithSignatures` The flow for sending the best router suggested by the router: - call `BuildTransactionsFromRoute` - wait for the `wallet.router.sign-transactions` signal - sign received hashes using `SignMessage` call or sign on keycard - call `SendRouterTransactionsWithSignatures` with the signatures of signed hashes from the previous step - `wallet.router.transactions-sent` signal will be sent after transactions are sent or if an error occurs New signals: - `wallet.router.sending-transactions-started` // notifies client that the sending transactions process started - `wallet.router.sign-transactions` // notifies client about the list of transactions that need to be signed - `wallet.router.transactions-sent` // notifies client about transactions that are sent - `wallet.transaction.status-changed` // notifies about status of sent transactions --- services/wallet/api.go | 190 ++++++++++ services/wallet/errors.go | 10 + .../router_build_transactions_params.go | 6 + .../wallet/requests/router_input_params.go | 1 + .../router_send_transactions_params.go | 8 + .../wallet/responses/router_transactions.go | 72 ++++ services/wallet/router/router.go | 14 + services/wallet/router/routes/route.go | 8 + services/wallet/router/routes/router_path.go | 115 ++++++ .../wallet/router/routes/router_path_test.go | 59 +++ .../transfer/commands_sequential_test.go | 2 +- services/wallet/transfer/errors.go | 13 + .../wallet/transfer/transaction_manager.go | 28 ++ .../transaction_manager_multitransaction.go | 1 + .../transfer/transaction_manager_route.go | 353 ++++++++++++++++++ signal/events_wallet.go | 10 +- transactions/transactor.go | 7 +- 17 files changed, 892 insertions(+), 5 deletions(-) create mode 100644 services/wallet/errors.go create mode 100644 services/wallet/requests/router_build_transactions_params.go create mode 100644 services/wallet/requests/router_send_transactions_params.go create mode 100644 services/wallet/responses/router_transactions.go create mode 100644 services/wallet/router/routes/router_path_test.go create mode 100644 services/wallet/transfer/errors.go create mode 100644 services/wallet/transfer/transaction_manager_route.go diff --git a/services/wallet/api.go b/services/wallet/api.go index eae8c7abe2b..93402387387 100644 --- a/services/wallet/api.go +++ b/services/wallet/api.go @@ -18,6 +18,7 @@ import ( signercore "github.com/ethereum/go-ethereum/signer/core/apitypes" abi_spec "github.com/status-im/status-go/abi-spec" "github.com/status-im/status-go/account" + statusErrors "github.com/status-im/status-go/errors" "github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/params" @@ -30,13 +31,16 @@ import ( "github.com/status-im/status-go/services/wallet/history" "github.com/status-im/status-go/services/wallet/onramp" "github.com/status-im/status-go/services/wallet/requests" + "github.com/status-im/status-go/services/wallet/responses" "github.com/status-im/status-go/services/wallet/router" "github.com/status-im/status-go/services/wallet/router/fees" "github.com/status-im/status-go/services/wallet/router/pathprocessor" + "github.com/status-im/status-go/services/wallet/router/sendtype" "github.com/status-im/status-go/services/wallet/thirdparty" "github.com/status-im/status-go/services/wallet/token" "github.com/status-im/status-go/services/wallet/transfer" "github.com/status-im/status-go/services/wallet/walletconnect" + "github.com/status-im/status-go/signal" "github.com/status-im/status-go/transactions" ) @@ -712,6 +716,15 @@ func (api *API) SendTransactionWithSignature(ctx context.Context, chainID uint64 return api.s.transactionManager.SendTransactionWithSignature(chainID, params, sig) } +// Deprecated: `CreateMultiTransaction` is the old way of sending transactions and should not be used anymore. +// +// The flow that should be used instead: +// - call `BuildTransactionsFromRoute` +// - wait for the `wallet.router.sign-transactions` signal +// - sign received hashes using `SignMessage` call or sign on keycard +// - call `SendRouterTransactionsWithSignatures` with the signatures of signed hashes from the previous step +// +// TODO: remove this struct once mobile switches to the new approach func (api *API) CreateMultiTransaction(ctx context.Context, multiTransactionCommand *transfer.MultiTransactionCommand, data []*pathprocessor.MultipathProcessorTxArgs, password string) (*transfer.MultiTransactionCommandResult, error) { log.Debug("[WalletAPI:: CreateMultiTransaction] create multi transaction") @@ -742,11 +755,188 @@ func (api *API) CreateMultiTransaction(ctx context.Context, multiTransactionComm return nil, api.s.transactionManager.SendTransactionForSigningToKeycard(ctx, cmd, data, api.router.GetPathProcessors()) } +func updateFields(sd *responses.SendDetails, inputParams requests.RouteInputParams) { + sd.SendType = int(inputParams.SendType) + sd.FromAddress = types.Address(inputParams.AddrFrom) + sd.ToAddress = types.Address(inputParams.AddrTo) + sd.FromToken = inputParams.TokenID + sd.ToToken = inputParams.ToTokenID + if inputParams.AmountIn != nil { + sd.FromAmount = inputParams.AmountIn.String() + } + if inputParams.AmountOut != nil { + sd.ToAmount = inputParams.AmountOut.String() + } + sd.OwnerTokenBeingSent = inputParams.TokenIDIsOwnerToken + sd.Username = inputParams.Username + sd.PublicKey = inputParams.PublicKey + if inputParams.PackID != nil { + sd.PackID = inputParams.PackID.String() + } +} + +func (api *API) BuildTransactionsFromRoute(ctx context.Context, buildInputParams *requests.RouterBuildTransactionsParams) { + log.Debug("[WalletAPI::BuildTransactionsFromRoute] builds transactions from the generated best route", "uuid", buildInputParams.Uuid) + + go func() { + api.router.StopSuggestedRoutesAsyncCalculation() + + var err error + response := &responses.RouterTransactionsForSigning{ + SendDetails: &responses.SendDetails{ + Uuid: buildInputParams.Uuid, + }, + } + + defer func() { + if err != nil { + api.s.transactionManager.ClearLocalRouterTransactionsData() + err = statusErrors.CreateErrorResponseFromError(err) + response.SendDetails.ErrorResponse = err.(*statusErrors.ErrorResponse) + } + signal.SendWalletEvent(signal.SignRouterTransactions, response) + }() + + route, routeInputParams := api.router.GetBestRouteAndAssociatedInputParams() + if routeInputParams.Uuid != buildInputParams.Uuid { + // should never be here + err = ErrCannotResolveRouteId + return + } + + updateFields(response.SendDetails, routeInputParams) + + // notify client that sending transactions started (has 3 steps, building txs, signing txs, sending txs) + signal.SendWalletEvent(signal.RouterSendingTransactionsStarted, response.SendDetails) + + response.SigningDetails, err = api.s.transactionManager.BuildTransactionsFromRoute( + route, + api.router.GetPathProcessors(), + transfer.BuildRouteExtraParams{ + AddressFrom: routeInputParams.AddrFrom, + AddressTo: routeInputParams.AddrTo, + Username: routeInputParams.Username, + PublicKey: routeInputParams.PublicKey, + PackID: routeInputParams.PackID.ToInt(), + SlippagePercentage: buildInputParams.SlippagePercentage, + }, + ) + }() +} + +// Deprecated: `ProceedWithTransactionsSignatures` is the endpoint used in the old way of sending transactions and should not be used anymore. +// +// The flow that should be used instead: +// - call `BuildTransactionsFromRoute` +// - wait for the `wallet.router.sign-transactions` signal +// - sign received hashes using `SignMessage` call or sign on keycard +// - call `SendRouterTransactionsWithSignatures` with the signatures of signed hashes from the previous step +// +// TODO: remove this struct once mobile switches to the new approach func (api *API) ProceedWithTransactionsSignatures(ctx context.Context, signatures map[string]transfer.SignatureDetails) (*transfer.MultiTransactionCommandResult, error) { log.Debug("[WalletAPI:: ProceedWithTransactionsSignatures] sign with signatures and send multi transaction") return api.s.transactionManager.ProceedWithTransactionsSignatures(ctx, signatures) } +func (api *API) SendRouterTransactionsWithSignatures(ctx context.Context, sendInputParams *requests.RouterSendTransactionsParams) { + log.Debug("[WalletAPI:: SendRouterTransactionsWithSignatures] sign with signatures and send") + go func() { + + var ( + err error + routeInputParams requests.RouteInputParams + ) + response := &responses.RouterSentTransactions{ + SendDetails: &responses.SendDetails{ + Uuid: sendInputParams.Uuid, + }, + } + + defer func() { + clearLocalData := true + if routeInputParams.SendType == sendtype.Swap { + // in case of swap don't clear local data if an approval is placed, but swap tx is not sent yet + if api.s.transactionManager.ApprovalRequiredForPath(pathprocessor.ProcessorSwapParaswapName) && + api.s.transactionManager.ApprovalPlacedForPath(pathprocessor.ProcessorSwapParaswapName) && + !api.s.transactionManager.TxPlacedForPath(pathprocessor.ProcessorSwapParaswapName) { + clearLocalData = false + } + } + + if clearLocalData { + api.s.transactionManager.ClearLocalRouterTransactionsData() + } + + if err != nil { + err = statusErrors.CreateErrorResponseFromError(err) + response.SendDetails.ErrorResponse = err.(*statusErrors.ErrorResponse) + } + signal.SendWalletEvent(signal.RouterTransactionsSent, response) + }() + + _, routeInputParams = api.router.GetBestRouteAndAssociatedInputParams() + if routeInputParams.Uuid != sendInputParams.Uuid { + err = ErrCannotResolveRouteId + return + } + + updateFields(response.SendDetails, routeInputParams) + + err = api.s.transactionManager.ValidateAndAddSignaturesToRouterTransactions(sendInputParams.Signatures) + if err != nil { + return + } + + ////////////////////////////////////////////////////////////////////////////// + // prepare multitx + var mtType transfer.MultiTransactionType = transfer.MultiTransactionSend + if routeInputParams.SendType == sendtype.Bridge { + mtType = transfer.MultiTransactionBridge + } else if routeInputParams.SendType == sendtype.Swap { + mtType = transfer.MultiTransactionSwap + } + + multiTx := transfer.NewMultiTransaction( + /* Timestamp: */ uint64(time.Now().Unix()), + /* FromNetworkID: */ 0, + /* ToNetworkID: */ 0, + /* FromTxHash: */ common.Hash{}, + /* ToTxHash: */ common.Hash{}, + /* FromAddress: */ routeInputParams.AddrFrom, + /* ToAddress: */ routeInputParams.AddrTo, + /* FromAsset: */ routeInputParams.TokenID, + /* ToAsset: */ routeInputParams.ToTokenID, + /* FromAmount: */ routeInputParams.AmountIn, + /* ToAmount: */ routeInputParams.AmountOut, + /* Type: */ mtType, + /* CrossTxID: */ "", + ) + + _, err = api.s.transactionManager.InsertMultiTransaction(multiTx) + if err != nil { + return + } + ////////////////////////////////////////////////////////////////////////////// + + response.SentTransactions, err = api.s.transactionManager.SendRouterTransactions(ctx, multiTx) + var ( + chainIDs []uint64 + addresses []common.Address + ) + for _, tx := range response.SentTransactions { + chainIDs = append(chainIDs, tx.FromChain) + addresses = append(addresses, common.Address(tx.FromAddress)) + go func(chainId uint64, txHash common.Hash) { + err = api.s.transactionManager.WatchTransaction(context.Background(), chainId, txHash) + if err != nil { + return + } + }(tx.FromChain, common.Hash(tx.Hash)) + } + err = api.s.transferController.CheckRecentHistory(chainIDs, addresses) + }() +} + func (api *API) GetMultiTransactions(ctx context.Context, transactionIDs []wcommon.MultiTransactionIDType) ([]*transfer.MultiTransaction, error) { log.Debug("wallet.api.GetMultiTransactions", "IDs.len", len(transactionIDs)) return api.s.transactionManager.GetMultiTransactions(ctx, transactionIDs) diff --git a/services/wallet/errors.go b/services/wallet/errors.go new file mode 100644 index 00000000000..77cd8bbe122 --- /dev/null +++ b/services/wallet/errors.go @@ -0,0 +1,10 @@ +package wallet + +import ( + "github.com/status-im/status-go/errors" +) + +// Abbreviation `W` for the error code stands for Wallet +var ( + ErrCannotResolveRouteId = &errors.ErrorResponse{Code: errors.ErrorCode("W-001"), Details: "cannot resolve route id"} +) diff --git a/services/wallet/requests/router_build_transactions_params.go b/services/wallet/requests/router_build_transactions_params.go new file mode 100644 index 00000000000..1090d9f7c98 --- /dev/null +++ b/services/wallet/requests/router_build_transactions_params.go @@ -0,0 +1,6 @@ +package requests + +type RouterBuildTransactionsParams struct { + Uuid string `json:"uuid"` + SlippagePercentage float32 `json:"slippagePercentage"` +} diff --git a/services/wallet/requests/router_input_params.go b/services/wallet/requests/router_input_params.go index 1ada0b5a54e..1130fdf3344 100644 --- a/services/wallet/requests/router_input_params.go +++ b/services/wallet/requests/router_input_params.go @@ -44,6 +44,7 @@ type RouteInputParams struct { 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"` diff --git a/services/wallet/requests/router_send_transactions_params.go b/services/wallet/requests/router_send_transactions_params.go new file mode 100644 index 00000000000..bc0de61c04f --- /dev/null +++ b/services/wallet/requests/router_send_transactions_params.go @@ -0,0 +1,8 @@ +package requests + +import "github.com/status-im/status-go/services/wallet/transfer" + +type RouterSendTransactionsParams struct { + Uuid string `json:"uuid"` + Signatures map[string]transfer.SignatureDetails `json:"signatures"` +} diff --git a/services/wallet/responses/router_transactions.go b/services/wallet/responses/router_transactions.go new file mode 100644 index 00000000000..1f277e42812 --- /dev/null +++ b/services/wallet/responses/router_transactions.go @@ -0,0 +1,72 @@ +package responses + +import ( + "github.com/status-im/status-go/errors" + "github.com/status-im/status-go/eth-node/types" + "github.com/status-im/status-go/transactions" +) + +type SendDetails struct { + Uuid string `json:"uuid"` + SendType int `json:"sendType"` + FromAddress types.Address `json:"fromAddress"` + ToAddress types.Address `json:"toAddress"` + FromToken string `json:"fromToken"` + ToToken string `json:"toToken"` + FromAmount string `json:"fromAmount"` // total amount + ToAmount string `json:"toAmount"` + OwnerTokenBeingSent bool `json:"ownerTokenBeingSent"` + ErrorResponse *errors.ErrorResponse `json:"errorResponse,omitempty"` + + Username string `json:"username"` + PublicKey string `json:"publicKey"` + PackID string `json:"packId"` +} + +type SigningDetails struct { + Address types.Address `json:"address"` + AddressPath string `json:"addressPath"` + KeyUid string `json:"keyUid"` + SignOnKeycard bool `json:"signOnKeycard"` + Hashes []types.Hash `json:"hashes"` +} + +type RouterTransactionsForSigning struct { + SendDetails *SendDetails `json:"sendDetails"` + SigningDetails *SigningDetails `json:"signingDetails"` +} + +type RouterSentTransaction struct { + FromAddress types.Address `json:"fromAddress"` + ToAddress types.Address `json:"toAddress"` + FromChain uint64 `json:"fromChain"` + ToChain uint64 `json:"toChain"` + FromToken string `json:"fromToken"` + ToToken string `json:"toToken"` + Amount string `json:"amount"` // amount of the transaction + Hash types.Hash `json:"hash"` + ApprovalTx bool `json:"approvalTx"` +} + +type RouterSentTransactions struct { + SendDetails *SendDetails `json:"sendDetails"` + SentTransactions []*RouterSentTransaction `json:"sentTransactions"` +} + +func NewRouterSentTransaction(sendArgs *transactions.SendTxArgs, hash types.Hash, approvalTx bool) *RouterSentTransaction { + addr := types.Address{} + if sendArgs.To != nil { + addr = *sendArgs.To + } + return &RouterSentTransaction{ + FromAddress: sendArgs.From, + ToAddress: addr, + FromChain: sendArgs.FromChainID, + ToChain: sendArgs.ToChainID, + FromToken: sendArgs.FromTokenID, + ToToken: sendArgs.ToTokenID, + Amount: sendArgs.Value.String(), + Hash: hash, + ApprovalTx: approvalTx, + } +} diff --git a/services/wallet/router/router.go b/services/wallet/router/router.go index 86dc58684c0..6b66c25e2be 100644 --- a/services/wallet/router/router.go +++ b/services/wallet/router/router.go @@ -120,6 +120,20 @@ func (r *Router) GetPathProcessors() map[string]pathprocessor.PathProcessor { return r.pathProcessors } +func (r *Router) GetBestRouteAndAssociatedInputParams() (routes.Route, requests.RouteInputParams) { + r.activeRoutesMutex.Lock() + defer r.activeRoutesMutex.Unlock() + if r.activeRoutes == nil { + return nil, requests.RouteInputParams{} + } + + r.lastInputParamsMutex.Lock() + defer r.lastInputParamsMutex.Unlock() + ip := *r.lastInputParams + + return r.activeRoutes.Best.Copy(), ip +} + func (r *Router) SetTestBalanceMap(balanceMap map[string]*big.Int) { for k, v := range balanceMap { r.activeBalanceMap.Store(k, v) diff --git a/services/wallet/router/routes/route.go b/services/wallet/router/routes/route.go index aa00ae00291..56057f0665c 100644 --- a/services/wallet/router/routes/route.go +++ b/services/wallet/router/routes/route.go @@ -9,6 +9,14 @@ import ( type Route []*Path +func (r Route) Copy() Route { + newRoute := make(Route, len(r)) + for i, path := range r { + newRoute[i] = path.Copy() + } + return newRoute +} + func FindBestRoute(routes []Route, tokenPrice float64, nativeTokenPrice float64) Route { var best Route bestCost := big.NewFloat(math.Inf(1)) diff --git a/services/wallet/router/routes/router_path.go b/services/wallet/router/routes/router_path.go index 1aa8ee42110..87b57bb3465 100644 --- a/services/wallet/router/routes/router_path.go +++ b/services/wallet/router/routes/router_path.go @@ -54,3 +54,118 @@ type Path struct { func (p *Path) Equal(o *Path) bool { return p.FromChain.ChainID == o.FromChain.ChainID && p.ToChain.ChainID == o.ToChain.ChainID } + +func (p *Path) Copy() *Path { + newPath := &Path{ + ProcessorName: p.ProcessorName, + AmountInLocked: p.AmountInLocked, + TxGasAmount: p.TxGasAmount, + ApprovalRequired: p.ApprovalRequired, + ApprovalGasAmount: p.ApprovalGasAmount, + EstimatedTime: p.EstimatedTime, + SubtractFees: p.SubtractFees, + } + + if p.FromChain != nil { + newPath.FromChain = ¶ms.Network{} + *newPath.FromChain = *p.FromChain + } + + if p.ToChain != nil { + newPath.ToChain = ¶ms.Network{} + *newPath.ToChain = *p.ToChain + } + + if p.FromToken != nil { + newPath.FromToken = &walletToken.Token{} + *newPath.FromToken = *p.FromToken + } + + if p.ToToken != nil { + newPath.ToToken = &walletToken.Token{} + *newPath.ToToken = *p.ToToken + } + + if p.AmountIn != nil { + newPath.AmountIn = (*hexutil.Big)(big.NewInt(0).Set(p.AmountIn.ToInt())) + } + + if p.AmountOut != nil { + newPath.AmountOut = (*hexutil.Big)(big.NewInt(0).Set(p.AmountOut.ToInt())) + } + + if p.SuggestedLevelsForMaxFeesPerGas != nil { + newPath.SuggestedLevelsForMaxFeesPerGas = &fees.MaxFeesLevels{ + Low: (*hexutil.Big)(big.NewInt(0).Set(p.SuggestedLevelsForMaxFeesPerGas.Low.ToInt())), + Medium: (*hexutil.Big)(big.NewInt(0).Set(p.SuggestedLevelsForMaxFeesPerGas.Medium.ToInt())), + High: (*hexutil.Big)(big.NewInt(0).Set(p.SuggestedLevelsForMaxFeesPerGas.High.ToInt())), + } + } + + if p.MaxFeesPerGas != nil { + newPath.MaxFeesPerGas = (*hexutil.Big)(big.NewInt(0).Set(p.MaxFeesPerGas.ToInt())) + } + + if p.TxBaseFee != nil { + newPath.TxBaseFee = (*hexutil.Big)(big.NewInt(0).Set(p.TxBaseFee.ToInt())) + } + + if p.TxPriorityFee != nil { + newPath.TxPriorityFee = (*hexutil.Big)(big.NewInt(0).Set(p.TxPriorityFee.ToInt())) + } + + if p.TxBonderFees != nil { + newPath.TxBonderFees = (*hexutil.Big)(big.NewInt(0).Set(p.TxBonderFees.ToInt())) + } + + if p.TxTokenFees != nil { + newPath.TxTokenFees = (*hexutil.Big)(big.NewInt(0).Set(p.TxTokenFees.ToInt())) + } + + if p.TxFee != nil { + newPath.TxFee = (*hexutil.Big)(big.NewInt(0).Set(p.TxFee.ToInt())) + } + + if p.TxL1Fee != nil { + newPath.TxL1Fee = (*hexutil.Big)(big.NewInt(0).Set(p.TxL1Fee.ToInt())) + } + + if p.ApprovalAmountRequired != nil { + newPath.ApprovalAmountRequired = (*hexutil.Big)(big.NewInt(0).Set(p.ApprovalAmountRequired.ToInt())) + } + + if p.ApprovalContractAddress != nil { + addr := common.HexToAddress(p.ApprovalContractAddress.Hex()) + newPath.ApprovalContractAddress = &addr + } + + if p.ApprovalBaseFee != nil { + newPath.ApprovalBaseFee = (*hexutil.Big)(big.NewInt(0).Set(p.ApprovalBaseFee.ToInt())) + } + + if p.ApprovalPriorityFee != nil { + newPath.ApprovalPriorityFee = (*hexutil.Big)(big.NewInt(0).Set(p.ApprovalPriorityFee.ToInt())) + } + + if p.ApprovalFee != nil { + newPath.ApprovalFee = (*hexutil.Big)(big.NewInt(0).Set(p.ApprovalFee.ToInt())) + } + + if p.ApprovalL1Fee != nil { + newPath.ApprovalL1Fee = (*hexutil.Big)(big.NewInt(0).Set(p.ApprovalL1Fee.ToInt())) + } + + if p.TxTotalFee != nil { + newPath.TxTotalFee = (*hexutil.Big)(big.NewInt(0).Set(p.TxTotalFee.ToInt())) + } + + if p.RequiredTokenBalance != nil { + newPath.RequiredTokenBalance = big.NewInt(0).Set(p.RequiredTokenBalance) + } + + if p.RequiredNativeBalance != nil { + newPath.RequiredNativeBalance = big.NewInt(0).Set(p.RequiredNativeBalance) + } + + return newPath +} diff --git a/services/wallet/router/routes/router_path_test.go b/services/wallet/router/routes/router_path_test.go new file mode 100644 index 00000000000..79cbe17e775 --- /dev/null +++ b/services/wallet/router/routes/router_path_test.go @@ -0,0 +1,59 @@ +package routes + +import ( + "math/big" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/status-im/status-go/params" + "github.com/status-im/status-go/services/wallet/router/fees" + "github.com/status-im/status-go/services/wallet/token" +) + +func TestCopyPath(t *testing.T) { + addr := common.HexToAddress("0x123") + path := &Path{ + ProcessorName: "test", + FromChain: ¶ms.Network{ChainID: 1}, + ToChain: ¶ms.Network{ChainID: 2}, + FromToken: &token.Token{Symbol: "symbol1"}, + ToToken: &token.Token{Symbol: "symbol2"}, + AmountIn: (*hexutil.Big)(big.NewInt(100)), + AmountInLocked: true, + AmountOut: (*hexutil.Big)(big.NewInt(200)), + SuggestedLevelsForMaxFeesPerGas: &fees.MaxFeesLevels{ + Low: (*hexutil.Big)(big.NewInt(100)), + Medium: (*hexutil.Big)(big.NewInt(200)), + High: (*hexutil.Big)(big.NewInt(300)), + }, + MaxFeesPerGas: (*hexutil.Big)(big.NewInt(100)), + TxBaseFee: (*hexutil.Big)(big.NewInt(100)), + TxPriorityFee: (*hexutil.Big)(big.NewInt(100)), + TxGasAmount: 100, + TxBonderFees: (*hexutil.Big)(big.NewInt(100)), + TxTokenFees: (*hexutil.Big)(big.NewInt(100)), + TxFee: (*hexutil.Big)(big.NewInt(100)), + TxL1Fee: (*hexutil.Big)(big.NewInt(100)), + ApprovalRequired: true, + ApprovalAmountRequired: (*hexutil.Big)(big.NewInt(100)), + ApprovalContractAddress: &addr, + ApprovalBaseFee: (*hexutil.Big)(big.NewInt(100)), + ApprovalPriorityFee: (*hexutil.Big)(big.NewInt(100)), + ApprovalGasAmount: 100, + ApprovalFee: (*hexutil.Big)(big.NewInt(100)), + ApprovalL1Fee: (*hexutil.Big)(big.NewInt(100)), + TxTotalFee: (*hexutil.Big)(big.NewInt(100)), + EstimatedTime: fees.TransactionEstimation(100), + RequiredTokenBalance: big.NewInt(100), + RequiredNativeBalance: big.NewInt(100), + SubtractFees: true, + } + + newPath := path.Copy() + + assert.True(t, reflect.DeepEqual(path, newPath)) +} diff --git a/services/wallet/transfer/commands_sequential_test.go b/services/wallet/transfer/commands_sequential_test.go index 6f23346df33..b463ada8daa 100644 --- a/services/wallet/transfer/commands_sequential_test.go +++ b/services/wallet/transfer/commands_sequential_test.go @@ -1325,7 +1325,7 @@ func TestFetchTransfersForLoadedBlocks(t *testing.T) { db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{}) require.NoError(t, err) - tm := &TransactionManager{NewMultiTransactionDB(db), nil, nil, nil, nil, nil, nil, nil, nil, nil} + tm := &TransactionManager{NewMultiTransactionDB(db), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil} mediaServer, err := server.NewMediaServer(appdb, nil, nil, db) require.NoError(t, err) diff --git a/services/wallet/transfer/errors.go b/services/wallet/transfer/errors.go new file mode 100644 index 00000000000..383f47fde0b --- /dev/null +++ b/services/wallet/transfer/errors.go @@ -0,0 +1,13 @@ +package transfer + +import ( + "github.com/status-im/status-go/errors" +) + +// Abbreviation `WT` for the error code stands for Wallet Transfer +var ( + ErrNoRoute = &errors.ErrorResponse{Code: errors.ErrorCode("WT-001"), Details: "no generated route"} + ErrNoTrsansactionsBeingBuilt = &errors.ErrorResponse{Code: errors.ErrorCode("WT-002"), Details: "no transactions being built"} + ErrMissingSignatureForTx = &errors.ErrorResponse{Code: errors.ErrorCode("WT-003"), Details: "missing signature for transaction %s"} + ErrInvalidSignatureDetails = &errors.ErrorResponse{Code: errors.ErrorCode("WT-004"), Details: "invalid signature details"} +) diff --git a/services/wallet/transfer/transaction_manager.go b/services/wallet/transfer/transaction_manager.go index c839d4f6cdb..9c77285ed96 100644 --- a/services/wallet/transfer/transaction_manager.go +++ b/services/wallet/transfer/transaction_manager.go @@ -17,6 +17,7 @@ import ( "github.com/status-im/status-go/params" wallet_common "github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/services/wallet/router/pathprocessor" + "github.com/status-im/status-go/services/wallet/router/routes" "github.com/status-im/status-go/transactions" ) @@ -26,6 +27,15 @@ type SignatureDetails struct { V string `json:"v"` } +func (sd *SignatureDetails) Validate() error { + if len(sd.R) != 64 || len(sd.S) != 64 || len(sd.V) != 2 { + return ErrInvalidSignatureDetails + } + + return nil +} + +// TODO: remove this struct once mobile switches to the new approach type TransactionDescription struct { chainID uint64 from common.Address @@ -33,6 +43,20 @@ type TransactionDescription struct { signature []byte } +type RouterTransactionDetails struct { + routerPath *routes.Path + txArgs *transactions.SendTxArgs + tx *ethTypes.Transaction + txHashToSign types.Hash + txSignature []byte + txSentHash types.Hash + approvalTxArgs *transactions.SendTxArgs + approvalTx *ethTypes.Transaction + approvalHashToSign types.Hash + approvalSignature []byte + approvalTxSentHash types.Hash +} + type TransactionManager struct { storage MultiTransactionStorage gethManager *account.GethManager @@ -42,9 +66,13 @@ type TransactionManager struct { pendingTracker *transactions.PendingTxTracker eventFeed *event.Feed + // TODO: remove this struct once mobile switches to the new approach multiTransactionForKeycardSigning *MultiTransaction multipathTransactionsData []*pathprocessor.MultipathProcessorTxArgs transactionsForKeycardSigning map[common.Hash]*TransactionDescription + + // used in a new approach + routerTransactions []*RouterTransactionDetails } type MultiTransactionStorage interface { diff --git a/services/wallet/transfer/transaction_manager_multitransaction.go b/services/wallet/transfer/transaction_manager_multitransaction.go index f2685bd3615..a678d590afe 100644 --- a/services/wallet/transfer/transaction_manager_multitransaction.go +++ b/services/wallet/transfer/transaction_manager_multitransaction.go @@ -198,6 +198,7 @@ func (tm *TransactionManager) WatchTransaction(ctx context.Context, chainID uint return err } if p.ChainID == wallet_common.ChainID(chainID) && p.Hash == transactionHash { + signal.SendWalletEvent(signal.TransactionStatusChanged, p) return nil } } diff --git a/services/wallet/transfer/transaction_manager_route.go b/services/wallet/transfer/transaction_manager_route.go new file mode 100644 index 00000000000..f885d73743e --- /dev/null +++ b/services/wallet/transfer/transaction_manager_route.go @@ -0,0 +1,353 @@ +package transfer + +import ( + "context" + "encoding/hex" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + ethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/status-im/status-go/errors" + "github.com/status-im/status-go/eth-node/crypto" + "github.com/status-im/status-go/eth-node/types" + walletCommon "github.com/status-im/status-go/services/wallet/common" + "github.com/status-im/status-go/services/wallet/responses" + "github.com/status-im/status-go/services/wallet/router/pathprocessor" + "github.com/status-im/status-go/services/wallet/router/routes" + "github.com/status-im/status-go/transactions" +) + +type BuildRouteExtraParams struct { + AddressFrom common.Address + AddressTo common.Address + Username string + PublicKey string + PackID *big.Int + SlippagePercentage float32 +} + +func (tm *TransactionManager) ClearLocalRouterTransactionsData() { + tm.routerTransactions = nil +} + +func (tm *TransactionManager) ApprovalRequiredForPath(pathProcessorName string) bool { + for _, desc := range tm.routerTransactions { + if desc.routerPath.ProcessorName == pathProcessorName && + desc.routerPath.ApprovalRequired { + return true + } + } + return false +} + +func (tm *TransactionManager) ApprovalPlacedForPath(pathProcessorName string) bool { + for _, desc := range tm.routerTransactions { + if desc.routerPath.ProcessorName == pathProcessorName && + desc.approvalTxSentHash != (types.Hash{}) { + return true + } + } + return false +} + +func (tm *TransactionManager) TxPlacedForPath(pathProcessorName string) bool { + for _, desc := range tm.routerTransactions { + if desc.routerPath.ProcessorName == pathProcessorName && + desc.txSentHash != (types.Hash{}) { + return true + } + } + return false +} + +func (tm *TransactionManager) buildApprovalTxForPath(path *routes.Path, addressFrom common.Address, + usedNonces map[uint64]int64, signer ethTypes.Signer) (types.Hash, error) { + lastUsedNonce := int64(-1) + if nonce, ok := usedNonces[path.FromChain.ChainID]; ok { + lastUsedNonce = nonce + } + + data, err := walletCommon.PackApprovalInputData(path.AmountIn.ToInt(), path.ApprovalContractAddress) + if err != nil { + return types.Hash{}, err + } + + addrTo := types.Address(path.FromToken.Address) + approavalSendArgs := &transactions.SendTxArgs{ + Version: transactions.SendTxArgsVersion1, + + // tx fields + From: types.Address(addressFrom), + To: &addrTo, + Value: (*hexutil.Big)(big.NewInt(0)), + Data: data, + Gas: (*hexutil.Uint64)(&path.ApprovalGasAmount), + MaxFeePerGas: path.MaxFeesPerGas, + MaxPriorityFeePerGas: path.ApprovalPriorityFee, + + // additional fields version 1 + FromChainID: path.FromChain.ChainID, + } + if path.FromToken != nil { + approavalSendArgs.FromTokenID = path.FromToken.Symbol + } + + builtApprovalTx, usedNonce, err := tm.transactor.ValidateAndBuildTransaction(approavalSendArgs.FromChainID, *approavalSendArgs, lastUsedNonce) + if err != nil { + return types.Hash{}, err + } + approvalTxHash := signer.Hash(builtApprovalTx) + usedNonces[path.FromChain.ChainID] = int64(usedNonce) + + tm.routerTransactions = append(tm.routerTransactions, &RouterTransactionDetails{ + routerPath: path, + approvalTxArgs: approavalSendArgs, + approvalTx: builtApprovalTx, + approvalHashToSign: types.Hash(approvalTxHash), + }) + + return types.Hash(approvalTxHash), nil +} + +func (tm *TransactionManager) buildTxForPath(path *routes.Path, pathProcessors map[string]pathprocessor.PathProcessor, + usedNonces map[uint64]int64, signer ethTypes.Signer, params BuildRouteExtraParams) (types.Hash, error) { + lastUsedNonce := int64(-1) + if nonce, ok := usedNonces[path.FromChain.ChainID]; ok { + lastUsedNonce = nonce + } + + processorInputParams := pathprocessor.ProcessorInputParams{ + FromAddr: params.AddressFrom, + ToAddr: params.AddressTo, + FromChain: path.FromChain, + ToChain: path.ToChain, + FromToken: path.FromToken, + ToToken: path.ToToken, + AmountIn: path.AmountIn.ToInt(), + AmountOut: path.AmountOut.ToInt(), + + Username: params.Username, + PublicKey: params.PublicKey, + PackID: params.PackID, + } + + data, err := pathProcessors[path.ProcessorName].PackTxInputData(processorInputParams) + if err != nil { + return types.Hash{}, err + } + + addrTo := types.Address(params.AddressTo) + sendArgs := &transactions.SendTxArgs{ + Version: transactions.SendTxArgsVersion1, + + // tx fields + From: types.Address(params.AddressFrom), + To: &addrTo, + Value: path.AmountIn, + Data: data, + Gas: (*hexutil.Uint64)(&path.TxGasAmount), + MaxFeePerGas: path.MaxFeesPerGas, + MaxPriorityFeePerGas: path.TxPriorityFee, + + // additional fields version 1 + ValueOut: path.AmountOut, + FromChainID: path.FromChain.ChainID, + ToChainID: path.ToChain.ChainID, + SlippagePercentage: params.SlippagePercentage, + } + if path.FromToken != nil { + sendArgs.FromTokenID = path.FromToken.Symbol + sendArgs.ToContractAddress = types.Address(path.FromToken.Address) + + // special handling for transfer tx if selected token is not ETH + // TODO: we should fix that in the trasactor, but till then, the best place to handle it is here + if !path.FromToken.IsNative() { + sendArgs.Value = (*hexutil.Big)(big.NewInt(0)) + + if path.ProcessorName == pathprocessor.ProcessorTransferName || + path.ProcessorName == pathprocessor.ProcessorStickersBuyName || + path.ProcessorName == pathprocessor.ProcessorENSRegisterName || + path.ProcessorName == pathprocessor.ProcessorENSReleaseName || + path.ProcessorName == pathprocessor.ProcessorENSPublicKeyName { + // TODO: update functions from `TransactorIface` to use `ToContractAddress` (as an address of the contract a transaction should be sent to) + // and `To` (as the destination address, recipient) of `SendTxArgs` struct appropriately + toContractAddr := types.Address(path.FromToken.Address) + sendArgs.To = &toContractAddr + } + } + } + if path.ToToken != nil { + sendArgs.ToTokenID = path.ToToken.Symbol + } + + builtTx, usedNonce, err := pathProcessors[path.ProcessorName].BuildTransactionV2(sendArgs, lastUsedNonce) + if err != nil { + return types.Hash{}, err + } + txHash := signer.Hash(builtTx) + usedNonces[path.FromChain.ChainID] = int64(usedNonce) + + tm.routerTransactions = append(tm.routerTransactions, &RouterTransactionDetails{ + routerPath: path, + txArgs: sendArgs, + tx: builtTx, + txHashToSign: types.Hash(txHash), + }) + + return types.Hash(txHash), nil +} + +func (tm *TransactionManager) BuildTransactionsFromRoute(route routes.Route, pathProcessors map[string]pathprocessor.PathProcessor, + params BuildRouteExtraParams) (*responses.SigningDetails, error) { + if len(route) == 0 { + return nil, ErrNoRoute + } + + accFrom, err := tm.accountsDB.GetAccountByAddress(types.Address(params.AddressFrom)) + if err != nil { + return nil, err + } + + keypair, err := tm.accountsDB.GetKeypairByKeyUID(accFrom.KeyUID) + if err != nil { + return nil, err + } + + response := &responses.SigningDetails{ + Address: accFrom.Address, + AddressPath: accFrom.Path, + KeyUid: accFrom.KeyUID, + SignOnKeycard: keypair.MigratedToKeycard(), + } + + usedNonces := make(map[uint64]int64) + for _, path := range route { + signer := ethTypes.NewLondonSigner(big.NewInt(int64(path.FromChain.ChainID))) + + // always check for approval tx first for the path and build it if needed + if path.ApprovalRequired && !tm.ApprovalPlacedForPath(path.ProcessorName) { + approvalTxHash, err := tm.buildApprovalTxForPath(path, params.AddressFrom, usedNonces, signer) + if err != nil { + return nil, err + } + response.Hashes = append(response.Hashes, approvalTxHash) + + // if approval is needed for swap, we cannot build the swap tx before the approval tx is mined + if path.ProcessorName == pathprocessor.ProcessorSwapParaswapName { + continue + } + } + + // build tx for the path + txHash, err := tm.buildTxForPath(path, pathProcessors, usedNonces, signer, params) + if err != nil { + return nil, err + } + response.Hashes = append(response.Hashes, txHash) + } + + return response, nil +} + +func getSignatureForTxHash(txHash string, signatures map[string]SignatureDetails) ([]byte, error) { + sigDetails, ok := signatures[txHash] + if !ok { + err := &errors.ErrorResponse{ + Code: ErrMissingSignatureForTx.Code, + Details: fmt.Sprintf(ErrMissingSignatureForTx.Details, txHash), + } + return nil, err + } + + err := sigDetails.Validate() + if err != nil { + return nil, err + } + + rBytes, _ := hex.DecodeString(sigDetails.R) + sBytes, _ := hex.DecodeString(sigDetails.S) + vByte := byte(0) + if sigDetails.V == "01" { + vByte = 1 + } + + signature := make([]byte, crypto.SignatureLength) + copy(signature[32-len(rBytes):32], rBytes) + copy(signature[64-len(rBytes):64], sBytes) + signature[64] = vByte + + return signature, nil +} + +func (tm *TransactionManager) ValidateAndAddSignaturesToRouterTransactions(signatures map[string]SignatureDetails) error { + if len(tm.routerTransactions) == 0 { + return ErrNoTrsansactionsBeingBuilt + } + + // check if all transactions have been signed + for _, desc := range tm.routerTransactions { + if desc.approvalTx != nil && desc.approvalTxSentHash == (types.Hash{}) { + sig, err := getSignatureForTxHash(desc.approvalHashToSign.String(), signatures) + if err != nil { + return err + } + desc.approvalSignature = sig + } + + if desc.tx != nil && desc.txSentHash == (types.Hash{}) { + sig, err := getSignatureForTxHash(desc.txHashToSign.String(), signatures) + if err != nil { + return err + } + desc.txSignature = sig + } + } + + return nil +} + +func (tm *TransactionManager) SendRouterTransactions(ctx context.Context, multiTx *MultiTransaction) (transactions []*responses.RouterSentTransaction, err error) { + transactions = make([]*responses.RouterSentTransaction, 0) + + // send transactions + for _, desc := range tm.routerTransactions { + if desc.approvalTx != nil && desc.approvalTxSentHash == (types.Hash{}) { + var approvalTxWithSignature *ethTypes.Transaction + approvalTxWithSignature, err = tm.transactor.AddSignatureToTransaction(desc.approvalTxArgs.FromChainID, desc.approvalTx, desc.approvalSignature) + if err != nil { + return nil, err + } + + desc.approvalTxSentHash, err = tm.transactor.SendTransactionWithSignature(common.Address(desc.approvalTxArgs.From), desc.approvalTxArgs.FromTokenID, multiTx.ID, approvalTxWithSignature) + if err != nil { + return nil, err + } + + transactions = append(transactions, responses.NewRouterSentTransaction(desc.approvalTxArgs, desc.approvalTxSentHash, true)) + + // if approval is needed for swap, then we need to wait for the approval tx to be mined before sending the swap tx + if desc.routerPath.ProcessorName == pathprocessor.ProcessorSwapParaswapName { + continue + } + } + + if desc.tx != nil && desc.txSentHash == (types.Hash{}) { + var txWithSignature *ethTypes.Transaction + txWithSignature, err = tm.transactor.AddSignatureToTransaction(desc.txArgs.FromChainID, desc.tx, desc.txSignature) + if err != nil { + return nil, err + } + + desc.txSentHash, err = tm.transactor.SendTransactionWithSignature(common.Address(desc.txArgs.From), desc.txArgs.FromTokenID, multiTx.ID, txWithSignature) + if err != nil { + return nil, err + } + + transactions = append(transactions, responses.NewRouterSentTransaction(desc.txArgs, desc.txSentHash, false)) + } + } + + return +} diff --git a/signal/events_wallet.go b/signal/events_wallet.go index 92595895865..02456aca8b8 100644 --- a/signal/events_wallet.go +++ b/signal/events_wallet.go @@ -3,9 +3,13 @@ package signal type SignalType string const ( - Wallet = SignalType("wallet") - SignTransactions = SignalType("wallet.sign.transactions") - SuggestedRoutes = SignalType("wallet.suggested.routes") + Wallet = SignalType("wallet") + SignTransactions = SignalType("wallet.sign.transactions") + RouterSendingTransactionsStarted = SignalType("wallet.router.sending-transactions-started") + SignRouterTransactions = SignalType("wallet.router.sign-transactions") + RouterTransactionsSent = SignalType("wallet.router.transactions-sent") + TransactionStatusChanged = SignalType("wallet.transaction.status-changed") + SuggestedRoutes = SignalType("wallet.suggested.routes") ) // SendWalletEvent sends event from services/wallet/events. diff --git a/transactions/transactor.go b/transactions/transactor.go index 766e9a21739..bf6193e5040 100644 --- a/transactions/transactor.go +++ b/transactions/transactor.go @@ -443,6 +443,11 @@ func (t *Transactor) validateAndBuildTransaction(rpcWrapper *rpcWrapper, args Se } func (t *Transactor) validateAndPropagate(rpcWrapper *rpcWrapper, selectedAccount *account.SelectedExtKey, args SendTxArgs, lastUsedNonce int64) (hash types.Hash, nonce uint64, err error) { + symbol := args.Symbol + if args.Version == SendTxArgsVersion1 { + symbol = args.FromTokenID + } + if err = t.validateAccount(args, selectedAccount); err != nil { return hash, nonce, err } @@ -458,7 +463,7 @@ func (t *Transactor) validateAndPropagate(rpcWrapper *rpcWrapper, selectedAccoun return hash, nonce, err } - hash, err = t.sendTransaction(rpcWrapper, common.Address(args.From), args.Symbol, args.MultiTransactionID, signedTx) + hash, err = t.sendTransaction(rpcWrapper, common.Address(args.From), symbol, args.MultiTransactionID, signedTx) return hash, tx.Nonce(), err } From 4773c62a26d0fec2a16cc518129c501df6f07e33 Mon Sep 17 00:00:00 2001 From: Anton Date: Wed, 25 Sep 2024 14:27:04 +0200 Subject: [PATCH 6/6] test_: added transaction from route test --- integration-tests/config.json | 15 ++- integration-tests/conftest.py | 10 +- integration-tests/docker-compose.anvil.yml | 2 +- .../docker-compose.test.status-go.yml | 5 + integration-tests/tests/test_cases.py | 54 +++++--- integration-tests/tests/test_router.py | 120 ++++++++++++++++++ integration-tests/tests/test_wallet_rpc.py | 3 +- services/wallet/common/const.go | 1 + services/wallet/router/router_updates.go | 22 +++- 9 files changed, 197 insertions(+), 35 deletions(-) create mode 100644 integration-tests/tests/test_router.py diff --git a/integration-tests/config.json b/integration-tests/config.json index 08b1520278e..387ba06b9ce 100644 --- a/integration-tests/config.json +++ b/integration-tests/config.json @@ -17,8 +17,17 @@ }, "Networks": [ { - "ChainID": 31337, - "RPCURL": "http://anvil:8545" - } + "ChainID": 31337, + "ChainName": "Anvil", + "DefaultRPCURL": "http://anvil:8545", + "RPCURL": "http://anvil:8545", + "ShortName": "eth", + "NativeCurrencyName": "Ether", + "NativeCurrencySymbol": "ETH", + "NativeCurrencyDecimals": 18, + "IsTest": false, + "Layer": 1, + "Enabled": true + } ] } diff --git a/integration-tests/conftest.py b/integration-tests/conftest.py index ec58edf3d0d..27b4a7546d4 100644 --- a/integration-tests/conftest.py +++ b/integration-tests/conftest.py @@ -21,6 +21,12 @@ def pytest_addoption(parser): help="", default="ws://0.0.0.0:8354", ) + parser.addoption( + "--anvil_url", + action="store", + help="", + default="http://0.0.0.0:8545", + ) parser.addoption( "--password", action="store", @@ -35,11 +41,11 @@ class Account(): private_key: str user_1 = Account( - address="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + address="0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", private_key="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", ) user_2 = Account( - address="0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + address="0x70997970c51812dc3a010c7d01b50e0d17dc79c8", private_key="0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", ) diff --git a/integration-tests/docker-compose.anvil.yml b/integration-tests/docker-compose.anvil.yml index 61dc6a1583a..141d8eda175 100644 --- a/integration-tests/docker-compose.anvil.yml +++ b/integration-tests/docker-compose.anvil.yml @@ -3,7 +3,7 @@ services: image: ghcr.io/foundry-rs/foundry:latest platform: linux/amd64 command: - - anvil --host 0.0.0.0 + - anvil --host 0.0.0.0 --block-time 2 deploy-sntv2: platform: linux/amd64 diff --git a/integration-tests/docker-compose.test.status-go.yml b/integration-tests/docker-compose.test.status-go.yml index 5b0b9b46653..ae2b01bfd47 100644 --- a/integration-tests/docker-compose.test.status-go.yml +++ b/integration-tests/docker-compose.test.status-go.yml @@ -16,6 +16,9 @@ services: "--password", "Strong12345", "--dir", "/tmp/status-go-data", # Keep in sync with `config.json/DataDir` value. Later this arg will not be needed. ] + ports: + - 3333:3333 + # - 8354:8354 # use for local debbuging only healthcheck: test: ["CMD-SHELL", "curl -X POST --data '{\"jsonrpc\":\"2.0\",\"method\":\"net_version\",\"params\":[],\"id\":1}' -H 'Content-Type: application/json' http://0.0.0.0:3333 || exit 1"] interval: 5s @@ -72,6 +75,8 @@ services: "-m", "wallet", "--rpc_url=http://status-go:3333", "--rpc_url_2=http://status-go-no-funds:3333", + "--anvil_url=http://anvil:8545", + "--ws_url=ws://status-go:8354", "--junitxml=/tests-rpc/reports/report.xml", ] volumes: diff --git a/integration-tests/tests/test_cases.py b/integration-tests/tests/test_cases.py index 81ebae27b27..338e935cdb3 100644 --- a/integration-tests/tests/test_cases.py +++ b/integration-tests/tests/test_cases.py @@ -3,6 +3,7 @@ import threading import logging import jsonschema +import time import requests from conftest import option, user_1, user_2 @@ -16,9 +17,11 @@ def _try_except_JSONDecodeError_KeyError(self, response, key: str): try: return response.json()[key] except json.JSONDecodeError: - raise AssertionError(f"Invalid JSON in response: {response.content}") + raise AssertionError( + f"Invalid JSON in response: {response.content}") except KeyError: - raise AssertionError(f"Key '{key}' not found in the JSON response.") + raise AssertionError( + f"Key '{key}' not found in the JSON response: {response.content}") def verify_is_valid_json_rpc_response(self, response, _id=None): assert response.status_code == 200 @@ -40,7 +43,6 @@ def verify_is_json_rpc_error(self, response): assert response.content self._try_except_JSONDecodeError_KeyError(response, "error") - def rpc_request(self, method, params=[], _id=None, client=None, url=None): client = client if client else requests.Session() url = url if url else option.rpc_url @@ -60,28 +62,27 @@ def verify_json_schema(self, response, method): schema=json.load(schema)) - class TransactionTestCase(RpcTestCase): - def wallet_create_multi_transaction(self, **kwargs): method = "wallet_createMultiTransaction" transferTx_data = { - "data": "", - "from": user_1.address, - "gas": "0x5BBF", - "input": "", - "maxFeePerGas": "0xbcc0f04fd", - "maxPriorityFeePerGas": "0xbcc0f04fd", - "to": user_2.address, - "type": "0x02", - "value": "0x5af3107a4000", - } + "data": "", + "from": user_1.address, + "gas": "0x5BBF", + "input": "", + "maxFeePerGas": "0xbcc0f04fd", + "maxPriorityFeePerGas": "0xbcc0f04fd", + "to": user_2.address, + "type": "0x02", + "value": "0x5af3107a4000", + } for key, new_value in kwargs.items(): if key in transferTx_data: transferTx_data[key] = new_value else: - print(f"Warning: The key '{key}' does not exist in the transferTx parameters and will be ignored.") + print( + f"Warning: The key '{key}' does not exist in the transferTx parameters and will be ignored.") params = [ { "fromAddress": user_1.address, @@ -116,10 +117,23 @@ def setup_method(self): class SignalTestCase(RpcTestCase): - received_signals = [] + await_signals = [] + received_signals = {} + + def on_message(self, ws, signal): + signal = json.loads(signal) + if signal.get("type") in self.await_signals: + self.received_signals[signal["type"]] = signal - def _on_message(self, ws, signal): - self.received_signals.append(signal) + def wait_for_signal(self, signal_type, timeout=10): + start_time = time.time() + while signal_type not in self.received_signals: + time_passed = time.time() - start_time + if time_passed >= timeout: + raise TimeoutError( + f"Signal {signal_type} is not received in {timeout} seconds") + time.sleep(0.5) + return self.received_signals[signal_type] def _on_error(self, ws, error): logging.info(f"Error: {error}") @@ -134,7 +148,7 @@ def _connect(self): self.url = f"{option.ws_url}/signals" ws = websocket.WebSocketApp(self.url, - on_message=self._on_message, + on_message=self.on_message, on_error=self._on_error, on_close=self._on_close) diff --git a/integration-tests/tests/test_router.py b/integration-tests/tests/test_router.py new file mode 100644 index 00000000000..55a3ab43d37 --- /dev/null +++ b/integration-tests/tests/test_router.py @@ -0,0 +1,120 @@ +import pytest +import time +import uuid +from conftest import user_1, user_2, option +from test_cases import SignalTestCase + + +@pytest.mark.rpc +@pytest.mark.transaction +@pytest.mark.wallet +class TestTransactionFromRoute(SignalTestCase): + + await_signals = [ + "wallet.suggested.routes", + "wallet.router.sign-transactions", + "wallet.router.sending-transactions-started", + "wallet.transaction.status-changed", + "wallet.router.transactions-sent" + ] + + def test_tx_from_route(self): + + _uuid = str(uuid.uuid4()) + amount_in = "0xde0b6b3a7640000" + + method = "wallet_getSuggestedRoutesAsync" + params = [ + { + "uuid": _uuid, + "sendType": 0, + "addrFrom": user_1.address, + "addrTo": user_2.address, + "amountIn": amount_in, + "amountOut": "0x0", + "tokenID": "ETH", + "tokenIDIsOwnerToken": False, + "toTokenID": "", + "disabledFromChainIDs": [10, 42161], + "disabledToChainIDs": [10, 42161], + "gasFeeMode": 1, + "fromLockedAmount": {} + } + ] + response = self.rpc_request(method, params) + self.verify_is_valid_json_rpc_response(response) + + routes = self.wait_for_signal("wallet.suggested.routes") + assert routes['event']['Uuid'] == _uuid + + method = "wallet_buildTransactionsFromRoute" + params = [ + { + "uuid": _uuid, + "slippagePercentage": 0 + } + ] + response = self.rpc_request(method, params) + self.verify_is_valid_json_rpc_response(response) + + self.wait_for_signal("wallet.router.sign-transactions") + + assert self.received_signals[ + 'wallet.router.sign-transactions']['event']['signingDetails']['signOnKeycard'] == False + transaction_hashes = self.received_signals[ + 'wallet.router.sign-transactions']['event']['signingDetails']['hashes'] + + assert transaction_hashes, "Transaction hashes are empty!" + + tx_signatures = {} + + for hash in transaction_hashes: + + method = "wallet_signMessage" + params = [ + hash, + user_1.address, + option.password + ] + + response = self.rpc_request(method, params) + self.verify_is_valid_json_rpc_response(response) + + if response.json()["result"].startswith("0x"): + tx_signature = response.json()["result"][2:] + + signature = { + "r": tx_signature[:64], + "s": tx_signature[64:128], + "v": tx_signature[128:] + } + + tx_signatures[hash] = signature + + method = "wallet_sendRouterTransactionsWithSignatures" + params = [ + { + "uuid": _uuid, + "Signatures": tx_signatures + } + ] + response = self.rpc_request(method, params) + self.verify_is_valid_json_rpc_response(response) + + tx_status = self.wait_for_signal("wallet.transaction.status-changed") + + + assert tx_status["event"]["chainId"] == 31337 + assert tx_status["event"]["status"] == "Success" + tx_hash = tx_status["event"]["hash"] + + method = "eth_getTransactionByHash" + params = [tx_hash] + + response = self.rpc_request(method, params, url=option.anvil_url) + self.verify_is_valid_json_rpc_response(response) + tx_details = response.json()["result"] + + assert tx_details["value"] == amount_in + assert tx_details["to"] == user_2.address + assert tx_details["from"] == user_1.address diff --git a/integration-tests/tests/test_wallet_rpc.py b/integration-tests/tests/test_wallet_rpc.py index 7d0f4d23e39..b212bec32ca 100644 --- a/integration-tests/tests/test_wallet_rpc.py +++ b/integration-tests/tests/test_wallet_rpc.py @@ -84,8 +84,7 @@ class TestRpc(RpcTestCase): ("wallet_getEthereumChains", []), ("wallet_getTokenList", []), ("wallet_getCryptoOnRamps", []), - ("wallet_getCachedCurrencyFormats", []), - ("wallet_fetchAllCurrencyFormats", []) + ("wallet_getCachedCurrencyFormats", []) ], ) def test_(self, method, params): diff --git a/services/wallet/common/const.go b/services/wallet/common/const.go index 3f60d44fa4a..f96cbf1c960 100644 --- a/services/wallet/common/const.go +++ b/services/wallet/common/const.go @@ -30,6 +30,7 @@ const ( ArbitrumSepolia uint64 = 421614 BinanceChainID uint64 = 56 // obsolete? BinanceTestChainID uint64 = 97 // obsolete? + AnvilMainnet uint64 = 31337 ) var ( diff --git a/services/wallet/router/router_updates.go b/services/wallet/router/router_updates.go index 217e2ef3680..f35b03effd0 100644 --- a/services/wallet/router/router_updates.go +++ b/services/wallet/router/router_updates.go @@ -11,11 +11,13 @@ import ( ) var ( - newBlockCheckIntervalMainnet = 3 * time.Second - newBlockCheckIntervalOptimism = 1 * time.Second - newBlockCheckIntervalArbitrum = 200 * time.Millisecond + newBlockCheckIntervalMainnet = 3 * time.Second + newBlockCheckIntervalOptimism = 1 * time.Second + newBlockCheckIntervalArbitrum = 200 * time.Millisecond + newBlockCheckIntervalAnvilMainnet = 2 * time.Second - feeRecalculationTimeout = 5 * time.Minute + feeRecalculationTimeout = 5 * time.Minute + feeRecalculationAnvilTimeout = 5 * time.Second ) type fetchingLastBlock struct { @@ -42,7 +44,11 @@ func (r *Router) subscribeForUdates(chainID uint64) error { } r.clientsForUpdatesPerChains.Store(chainID, flb) - r.startTimeoutForUpdates(flb.closeCh) + timeout := feeRecalculationTimeout + if chainID == walletCommon.AnvilMainnet { + timeout = feeRecalculationAnvilTimeout + } + r.startTimeoutForUpdates(flb.closeCh, timeout) var ticker *time.Ticker switch chainID { @@ -55,6 +61,8 @@ func (r *Router) subscribeForUdates(chainID uint64) error { case walletCommon.ArbitrumMainnet, walletCommon.ArbitrumSepolia: ticker = time.NewTicker(newBlockCheckIntervalArbitrum) + case walletCommon.AnvilMainnet: + ticker = time.NewTicker(newBlockCheckIntervalAnvilMainnet) } ctx, cancelCtx := context.WithCancel(context.Background()) @@ -123,8 +131,8 @@ func (r *Router) subscribeForUdates(chainID uint64) error { return nil } -func (r *Router) startTimeoutForUpdates(closeCh chan struct{}) { - dedlineTicker := time.NewTicker(feeRecalculationTimeout) +func (r *Router) startTimeoutForUpdates(closeCh chan struct{}, timeout time.Duration) { + dedlineTicker := time.NewTicker(timeout) go func() { defer gocommon.LogOnPanic() for {