Skip to content

Commit

Permalink
Merge pull request #1443 from 0chain/feature/gas-estimation
Browse files Browse the repository at this point in the history
Feature: add gas amount estimation feature to zcnbridge
  • Loading branch information
dabasov authored Mar 28, 2024
2 parents 2af64fe + 6248386 commit f3ecab4
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 45 deletions.
4 changes: 1 addition & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -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
Expand Down
7 changes: 0 additions & 7 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -158,15 +157,13 @@ 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=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
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=
Expand Down Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
34 changes: 34 additions & 0 deletions wasmsdk/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions wasmsdk/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ func main() {
"getMintWZCNPayload": getMintWZCNPayload,
"getNotProcessedWZCNBurnEvents": getNotProcessedWZCNBurnEvents,
"getNotProcessedZCNBurnTickets": getNotProcessedZCNBurnTickets,
"estimateBurnWZCNGasAmount": estimateBurnWZCNGasAmount,
"estimateMintWZCNGasAmount": estimateMintWZCNGasAmount,
"estimateGasPrice": estimateGasPrice,

//zcn
Expand Down
148 changes: 124 additions & 24 deletions zcnbridge/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -998,66 +998,166 @@ 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)
gasAmountInt.SetString(gasAmountRaw)

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)
gasPriceInt.SetString(gasPriceRaw)

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")
}
10 changes: 3 additions & 7 deletions zcnbridge/bridge_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 12 additions & 4 deletions zcnbridge/bridge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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)

Expand Down
6 changes: 6 additions & 0 deletions zcnbridge/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ import (
"github.com/spf13/viper"
)

const (
TenderlyProvider = iota
AlchemyProvider
UnknownProvider
)

const (
ZChainsClientConfigName = "config.yaml"
ZChainWalletConfigName = "wallet.json"
Expand Down

0 comments on commit f3ecab4

Please sign in to comment.