diff --git a/go.mod b/go.mod index 28e7e6799..d82593b7b 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/0chain/gosdk -go 1.21 - -toolchain go1.21.5 +go 1.20 require ( github.com/0chain/common v0.0.6-0.20230127095721-8df4d1d72565 diff --git a/go.sum b/go.sum index 616bf4e1c..aaf77d1b2 100644 --- a/go.sum +++ b/go.sum @@ -99,7 +99,6 @@ github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46f github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= -github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -158,7 +157,6 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/ethereum/go-ethereum v1.10.26 h1:i/7d9RBBwiXCEuyduBQzJw/mKmnvzsN14jqBmytw72s= github.com/ethereum/go-ethereum v1.10.26/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= @@ -166,7 +164,6 @@ github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlK github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -254,7 +251,6 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -291,7 +287,6 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= -github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0= github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -577,7 +572,6 @@ go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= @@ -830,7 +824,6 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= diff --git a/wasmsdk/bridge.go b/wasmsdk/bridge.go index be70669f5..a76b84d68 100644 --- a/wasmsdk/bridge.go +++ b/wasmsdk/bridge.go @@ -173,6 +173,40 @@ func getNotProcessedZCNBurnTickets() string { return string(result) } +// estimateBurnWZCNGasAmount performs gas amount estimation for the given burn wzcn transaction. +func estimateBurnWZCNGasAmount(from, to string, amountTokens int) string { // nolint:golint,unused + estimateBurnWZCNGasAmountResponse, err := bridge.EstimateBurnWZCNGasAmount( + context.Background(), from, to, amountTokens) + if err != nil { + return errors.Wrap("estimateBurnWZCNGasAmount", "failed to estimate gas amount", err).Error() + } + + var result []byte + result, err = json.Marshal(estimateBurnWZCNGasAmountResponse) + if err != nil { + return errors.Wrap("estimateBurnWZCNGasAmount", "failed to marshal gas amount estimation result", err).Error() + } + + return string(result) +} + +// estimateMintWZCNGasAmount performs gas amount estimation for the given mint wzcn transaction. +func estimateMintWZCNGasAmount(from, to, zcnTransaction string, amountToken, nonce int64, signatures []string) string { // nolint:golint,unused + estimateMintWZCNGasAmountResponse, err := bridge.EstimateMintWZCNGasAmount( + context.Background(), from, to, zcnTransaction, amountToken, nonce, signatures) + if err != nil { + return errors.Wrap("estimateMintWZCNGasAmount", "failed to estimate gas amount", err).Error() + } + + var result []byte + result, err = json.Marshal(estimateMintWZCNGasAmountResponse) + if err != nil { + return errors.Wrap("estimateMintWZCNGasAmount", "failed to marshal gas amount estimation result", err).Error() + } + + return string(result) +} + // estimateGasPrice performs gas estimation for the given transaction using Alchemy enhanced API returning // approximate final gas fee. func estimateGasPrice(from, to string, value int64) string { // nolint:golint,unused diff --git a/wasmsdk/proxy.go b/wasmsdk/proxy.go index af35ab095..971a013f4 100644 --- a/wasmsdk/proxy.go +++ b/wasmsdk/proxy.go @@ -236,6 +236,8 @@ func main() { "getMintWZCNPayload": getMintWZCNPayload, "getNotProcessedWZCNBurnEvents": getNotProcessedWZCNBurnEvents, "getNotProcessedZCNBurnTickets": getNotProcessedZCNBurnTickets, + "estimateBurnWZCNGasAmount": estimateBurnWZCNGasAmount, + "estimateMintWZCNGasAmount": estimateMintWZCNGasAmount, "estimateGasPrice": estimateGasPrice, //zcn diff --git a/zcnbridge/bridge.go b/zcnbridge/bridge.go index eec3623bc..041b2413e 100644 --- a/zcnbridge/bridge.go +++ b/zcnbridge/bridge.go @@ -998,35 +998,44 @@ func (b *BridgeClient) prepareBridge(ctx context.Context, ethereumAddress, metho return bridgeInstance, transactOpts, nil } -// isEstimateGasPriceAvailable checks if currently selected ethereum node url can be used for gas estimation. -func (b *BridgeClient) isEstimateGasPriceAvailable() bool { - return strings.Contains(b.EthereumNodeURL, "eth-mainnet.g.alchemy.com") +// getProviderType validates the provider url and exposes pre-defined type definition. +func (b *BridgeClient) getProviderType() int { + if strings.Contains(b.EthereumNodeURL, "g.alchemy.com") { + return AlchemyProvider + } else if strings.Contains(b.EthereumNodeURL, "rpc.tenderly.co") { + return TenderlyProvider + } else { + return UnknownProvider + } } -// EstimateGasPrice performs gas estimation for the given transaction using Alchemy enhanced API returning -// approximate final gas fee. -func (b *BridgeClient) EstimateGasPrice(ctx context.Context, from, to string, value int64) (*GasPriceEstimationResult, error) { - if !b.isEstimateGasPriceAvailable() { - return nil, errors.New("used json-rpc does not allow to estimate gas price") - } +// estimateTenderlyGasAmount performs gas amount estimation for the given transaction using Tenderly provider. +func (b *BridgeClient) estimateTenderlyGasAmount(ctx context.Context, from, to string, value int64) (float64, error) { + return 8000000, nil +} +// estimateAlchemyGasAmount performs gas amount estimation for the given transaction using Alchemy provider +func (b *BridgeClient) estimateAlchemyGasAmount(ctx context.Context, from, to, data string, value int64) (float64, error) { client := jsonrpc.NewClient(b.EthereumNodeURL) valueHex := ConvertIntToHex(value) - resp, err := client.Call(ctx, "eth_estimateGas", &GasEstimationRequest{ - From: from, To: to, Value: valueHex}) + resp, err := client.Call(ctx, "eth_estimateGas", &AlchemyGasEstimationRequest{ + From: from, + To: to, + Value: valueHex, + Data: data}) if err != nil { - return nil, errors.Wrap(err, "gas price estimation failed") + return 0, errors.Wrap(err, "gas price estimation failed") } if resp.Error != nil { - return nil, errors.Wrap(errors.New(resp.Error.Error()), "gas price estimation failed") + return 0, errors.Wrap(errors.New(resp.Error.Error()), "gas price estimation failed") } gasAmountRaw, ok := resp.Result.(string) if !ok { - return nil, errors.New("failed to parse gas amount") + return 0, errors.New("failed to parse gas amount") } gasAmountInt := new(big.Float) @@ -1034,21 +1043,102 @@ func (b *BridgeClient) EstimateGasPrice(ctx context.Context, from, to string, va gasAmountFloat, _ := gasAmountInt.Float64() - fmt.Println(gasAmountFloat) + return gasAmountFloat, nil +} + +// EstimateBurnWZCNGasAmount performs gas amount estimation for the given wzcn burn transaction. +func (b *BridgeClient) EstimateBurnWZCNGasAmount(ctx context.Context, from, to string, amountTokens int) (float64, error) { + switch b.getProviderType() { + case AlchemyProvider: + abi, err := bridge.BridgeMetaData.GetAbi() + if err != nil { + return 0, errors.Wrap(err, "failed to get ABI") + } + + clientID := DefaultClientIDEncoder(zcncore.GetClientWalletID()) + + amount := new(big.Int) + amount.SetInt64(int64(amountTokens)) - resp, err = client.Call(ctx, "eth_gasPrice") + var packRaw []byte + packRaw, err = abi.Pack("burn", amount, clientID) + if err != nil { + return 0, errors.Wrap(err, "failed to pack arguments") + } + + pack := "0x" + hex.EncodeToString(packRaw) + + return b.estimateAlchemyGasAmount(ctx, from, to, pack, 0) + case TenderlyProvider: + return b.estimateTenderlyGasAmount(ctx, from, to, 0) + } + + return 0, errors.New("used json-rpc does not allow to estimate gas amount") +} + +// EstimateMintWZCNGasAmount performs gas amount estimation for the given wzcn mint transaction. +func (b *BridgeClient) EstimateMintWZCNGasAmount( + ctx context.Context, from, to, zcnTransactionRaw string, amountToken, nonceRaw int64, signaturesRaw []string) (float64, error) { + switch b.getProviderType() { + case AlchemyProvider: + amount := new(big.Int) + amount.SetInt64(amountToken) + + zcnTransaction := DefaultClientIDEncoder(zcnTransactionRaw) + + nonce := new(big.Int) + nonce.SetInt64(nonceRaw) + + var signatures [][]byte + for _, signature := range signaturesRaw { + signatures = append(signatures, []byte(signature)) + } + + fromRaw := common.HexToAddress(from) + + abi, err := bridge.BridgeMetaData.GetAbi() + if err != nil { + return 0, errors.Wrap(err, "failed to get ABI") + } + + var packRaw []byte + packRaw, err = abi.Pack("mint", fromRaw, amount, zcnTransaction, nonce, signatures) + if err != nil { + return 0, errors.Wrap(err, "failed to pack arguments") + } + + pack := "0x" + hex.EncodeToString(packRaw) + + return b.estimateAlchemyGasAmount(ctx, from, to, pack, 0) + case TenderlyProvider: + return b.estimateTenderlyGasAmount(ctx, from, to, 0) + } + + return 0, errors.New("used json-rpc does not allow to estimate gas amount") +} + +// estimateTenderlyGasPrice performs gas estimation for the given transaction using Tenderly API. +func (b *BridgeClient) estimateTenderlyGasPrice(ctx context.Context, from, to string, value int64) (float64, error) { + return 0, nil +} + +// estimateAlchemyGasPrice performs gas estimation for the given transaction using Alchemy enhanced API returning +// approximate final gas fee. +func (b *BridgeClient) estimateAlchemyGasPrice(ctx context.Context, from, to string, value int64) (float64, error) { + client := jsonrpc.NewClient(b.EthereumNodeURL) + + resp, err := client.Call(ctx, "eth_gasPrice") if err != nil { - return nil, errors.Wrap(err, "gas price estimation failed") + return 0, errors.Wrap(err, "gas price estimation failed") } if resp.Error != nil { - return nil, errors.Wrap(errors.New(resp.Error.Error()), "gas price estimation failed") + return 0, errors.Wrap(errors.New(resp.Error.Error()), "gas price estimation failed") } - var gasPriceRaw string - gasPriceRaw, ok = resp.Result.(string) + gasPriceRaw, ok := resp.Result.(string) if !ok { - return nil, errors.New("failed to parse gas price") + return 0, errors.New("failed to parse gas price") } gasPriceInt := new(big.Float) @@ -1056,8 +1146,18 @@ func (b *BridgeClient) EstimateGasPrice(ctx context.Context, from, to string, va gasPriceFloat, _ := gasPriceInt.Float64() - fmt.Println(gasPriceFloat) + return gasPriceFloat, nil +} + +// EstimateGasPrice performs gas estimation for the given transaction using Alchemy enhanced API returning +// approximate final gas fee. +func (b *BridgeClient) EstimateGasPrice(ctx context.Context, from, to string, value int64) (float64, error) { + switch b.getProviderType() { + case AlchemyProvider: + return b.estimateAlchemyGasPrice(ctx, from, to, value) + case TenderlyProvider: + return b.estimateTenderlyGasPrice(ctx, from, to, value) + } - return &GasPriceEstimationResult{ - Value: gasPriceFloat * gasAmountFloat}, nil + return 0, errors.New("used json-rpc does not allow to estimate gas price") } diff --git a/zcnbridge/bridge_helper.go b/zcnbridge/bridge_helper.go index 08014f445..ffe038c50 100644 --- a/zcnbridge/bridge_helper.go +++ b/zcnbridge/bridge_helper.go @@ -10,16 +10,12 @@ import ( "github.com/pkg/errors" ) -// GasEstimationRequest describes request used for Alchemy enhanced JSON-RPC API. -type GasEstimationRequest struct { +// AlchemyGasEstimationRequest describes request used for Alchemy enhanced JSON-RPC API. +type AlchemyGasEstimationRequest struct { From string `json:"from"` To string `json:"to"` Value string `json:"value"` -} - -// GasPriceEstimationResult represents result of the gas price estimation operation execution. -type GasPriceEstimationResult struct { - Value float64 `json:"value"` + Data string `json:"data"` } // BancorTokenDetails describes Bancor ZCN zcntoken pool details diff --git a/zcnbridge/bridge_test.go b/zcnbridge/bridge_test.go index 51a750dec..75e06cf7d 100644 --- a/zcnbridge/bridge_test.go +++ b/zcnbridge/bridge_test.go @@ -41,9 +41,10 @@ import ( const ( ethereumAddress = "0xD8c9156e782C68EE671C09b6b92de76C97948432" - alchemyEthereumNodeURL = "https://eth-mainnet.g.alchemy.com/v2/9VanLUbRE0pLmDHwCHGJlhs9GHosrfD9" - infuraEthereumNodeURL = "https://mainnet.infura.io/v3/7238211010344719ad14a89db874158c" - value = 1e+10 + alchemyEthereumNodeURL = "https://eth-mainnet.g.alchemy.com/v2/9VanLUbRE0pLmDHwCHGJlhs9GHosrfD9" + tenderlyEthereumNodeURL = "https://rpc.tenderly.co/fork/835ecb4e-1f60-4129-adc2-b0c741193839" + infuraEthereumNodeURL = "https://mainnet.infura.io/v3/7238211010344719ad14a89db874158c" + value = 1e+10 password = "02289b9" @@ -628,13 +629,20 @@ func Test_ZCNBridge(t *testing.T) { )) }) - t.Run("should check if gas price estimation works with correct ethereum node url", func(t *testing.T) { + t.Run("should check if gas price estimation works with correct alchemy ethereum node url", func(t *testing.T) { bridgeClient = getBridgeClient(bancorMockServerURL, alchemyEthereumNodeURL, ethereumClient, transactionProvider, keyStore) _, err := bridgeClient.EstimateGasPrice(context.Background(), tokenAddress, bridgeAddress, value) require.Contains(t, err.Error(), "Must be authenticated!") }) + t.Run("should check if gas price estimation works with correct tenderly ethereum node url", func(t *testing.T) { + bridgeClient = getBridgeClient(bancorMockServerURL, tenderlyEthereumNodeURL, ethereumClient, transactionProvider, keyStore) + + _, err := bridgeClient.EstimateGasPrice(context.Background(), tokenAddress, bridgeAddress, value) + require.NoError(t, err) + }) + t.Run("should check if gas price estimation works with incorrect ethereum node url", func(t *testing.T) { bridgeClient = getBridgeClient(bancorMockServerURL, infuraEthereumNodeURL, ethereumClient, transactionProvider, keyStore) diff --git a/zcnbridge/config.go b/zcnbridge/config.go index 03ba097ba..f01e3e0aa 100644 --- a/zcnbridge/config.go +++ b/zcnbridge/config.go @@ -15,6 +15,12 @@ import ( "github.com/spf13/viper" ) +const ( + TenderlyProvider = iota + AlchemyProvider + UnknownProvider +) + const ( ZChainsClientConfigName = "config.yaml" ZChainWalletConfigName = "wallet.json"