Skip to content

Commit e39a681

Browse files
authored
Merge pull request #60 from keep-network/ethers-units
Parse ether value from a text In a configuration file, we are providing value for MaxGasPrice and BalanceAlertThreshold. If we provide it in wei we are limited to around 9.22 ether. To be more flexible and allow providing greater value especially for balance monitoring we added a possibility to provide the value as a string with three types of units: wei, Gwei and ether. We are also supporting decimals. Few exaples of how the value can be provided: BalanceAlertThreshold = 100000000000 BalanceAlertThreshold = "100000000000 wei" BalanceAlertThreshold = "100 Gwei" BalanceAlertThreshold = "2 ether" BalanceAlertThreshold = "3.4 ether"
2 parents e3f1963 + 13b8bae commit e39a681

File tree

6 files changed

+250
-9
lines changed

6 files changed

+250
-9
lines changed

pkg/chain/ethereum/config.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ type Config struct {
3939
// time, the gas price is increased and transaction is resubmitted.
4040
MiningCheckInterval int
4141

42-
// MaxGasPrice specifies the maximum gas price the client is
42+
// MaxGasPrice specifies the maximum gas price (in wei) the client is
4343
// willing to pay for the transaction to be mined. The offered transaction
4444
// gas price can not be higher than the max gas price value. If the maximum
4545
// allowed gas price is reached, no further resubmission attempts are
4646
// performed.
47-
MaxGasPrice uint64
47+
MaxGasPrice *Wei
4848

4949
// RequestsPerSecondLimit sets the maximum average number of requests
5050
// per second which can be executed against the Ethereum node.
@@ -58,9 +58,9 @@ type Config struct {
5858
// including view function calls.
5959
ConcurrencyLimit int
6060

61-
// BalanceAlertThreshold defines a minimum value of the operator's account
61+
// BalanceAlertThreshold defines a minimum value (wei) of the operator's account
6262
// balance below which an alert will be triggered.
63-
BalanceAlertThreshold uint64
63+
BalanceAlertThreshold *Wei
6464
}
6565

6666
// ContractAddress finds a given contract's address configuration and returns it

pkg/chain/ethereum/wei.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package ethereum
2+
3+
import (
4+
"fmt"
5+
"math/big"
6+
"regexp"
7+
"strings"
8+
9+
"github.com/ethereum/go-ethereum/params"
10+
)
11+
12+
// Wei is a custom type to handle Ether value parsing in configuration files
13+
// using BurntSushi/toml package. It supports wei, Gwei and ether units. The
14+
// Ether value is kept as `wei` and `wei` is the default unit.
15+
// The value can be provided in the text file as e.g.: `1 wei`, `200 Gwei` or
16+
// `0.5 ether`.
17+
type Wei struct {
18+
*big.Int
19+
}
20+
21+
// The most common units for ether values.
22+
const (
23+
wei unit = iota
24+
gwei
25+
ether
26+
)
27+
28+
// unit represents Ether value unit.
29+
type unit int
30+
31+
func (u unit) String() string {
32+
return [...]string{"wei", "Gwei", "ether"}[u]
33+
}
34+
35+
// UnmarshalText is a function used to parse a value of Ethers.
36+
func (e *Wei) UnmarshalText(text []byte) error {
37+
re := regexp.MustCompile(`^(\d+[\.]?[\d]*)[ ]?([\w]*)$`)
38+
matched := re.FindSubmatch(text)
39+
40+
if len(matched) != 3 {
41+
return fmt.Errorf("failed to parse value: [%s]", text)
42+
}
43+
44+
number, ok := new(big.Float).SetString(string(matched[1]))
45+
if !ok {
46+
return fmt.Errorf(
47+
"failed to set float value from string [%s]",
48+
string(matched[1]),
49+
)
50+
}
51+
52+
unit := matched[2]
53+
if len(unit) == 0 {
54+
unit = []byte("wei")
55+
}
56+
57+
switch strings.ToLower(string(unit)) {
58+
case strings.ToLower(ether.String()):
59+
number.Mul(number, big.NewFloat(params.Ether))
60+
e.Int, _ = number.Int(nil)
61+
case strings.ToLower(gwei.String()):
62+
number.Mul(number, big.NewFloat(params.GWei))
63+
e.Int, _ = number.Int(nil)
64+
case strings.ToLower(wei.String()):
65+
number.Mul(number, big.NewFloat(params.Wei))
66+
e.Int, _ = number.Int(nil)
67+
default:
68+
return fmt.Errorf(
69+
"invalid unit: %s; please use one of: wei, Gwei, ether",
70+
unit,
71+
)
72+
}
73+
74+
return nil
75+
}

pkg/chain/ethereum/wei_test.go

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
package ethereum
2+
3+
import (
4+
"fmt"
5+
"math/big"
6+
"reflect"
7+
"testing"
8+
)
9+
10+
func TestUnmarshalText(t *testing.T) {
11+
int5000ether, _ := new(big.Int).SetString("5000000000000000000000", 10)
12+
13+
var tests = map[string]struct {
14+
value string
15+
expectedResult *big.Int
16+
expectedError error
17+
}{
18+
"decimal value": {
19+
value: "0.9",
20+
expectedResult: big.NewInt(0),
21+
},
22+
"lowest value": {
23+
value: "1",
24+
expectedResult: big.NewInt(1),
25+
},
26+
"missing unit": {
27+
value: "702",
28+
expectedResult: big.NewInt(702),
29+
},
30+
"unit: wei": {
31+
value: "4 wei",
32+
expectedResult: big.NewInt(4),
33+
},
34+
"unit: gwei": {
35+
value: "30 gwei",
36+
expectedResult: big.NewInt(30000000000),
37+
},
38+
"unit: ether": {
39+
value: "2 ether",
40+
expectedResult: big.NewInt(2000000000000000000),
41+
},
42+
"unit: mixed case": {
43+
value: "5 GWei",
44+
expectedResult: big.NewInt(5000000000),
45+
},
46+
"decimal wei": {
47+
value: "2.9 wei",
48+
expectedResult: big.NewInt(2),
49+
},
50+
"decimal ether": {
51+
value: "0.8 ether",
52+
expectedResult: big.NewInt(800000000000000000),
53+
},
54+
"multiple decimal digits": {
55+
value: "5.6789 Gwei",
56+
expectedResult: big.NewInt(5678900000),
57+
},
58+
"missing decimal digit": {
59+
value: "6. Gwei",
60+
expectedResult: big.NewInt(6000000000),
61+
},
62+
"no space": {
63+
value: "9ether",
64+
expectedResult: big.NewInt(9000000000000000000),
65+
},
66+
"int overflow amount": {
67+
value: "5000000000000000000000",
68+
expectedResult: int5000ether,
69+
},
70+
"int overflow amount after conversion": {
71+
value: "5000 ether",
72+
expectedResult: int5000ether,
73+
},
74+
"double space": {
75+
value: "100 Gwei",
76+
expectedError: fmt.Errorf("failed to parse value: [100 Gwei]"),
77+
},
78+
"leading space": {
79+
value: " 3 wei",
80+
expectedError: fmt.Errorf("failed to parse value: [ 3 wei]"),
81+
},
82+
"trailing space": {
83+
value: "3 wei ",
84+
expectedError: fmt.Errorf("failed to parse value: [3 wei ]"),
85+
},
86+
87+
"invalid comma delimeter": {
88+
value: "3,5 ether",
89+
expectedError: fmt.Errorf("failed to parse value: [3,5 ether]"),
90+
},
91+
"only decimal number": {
92+
value: ".7 Gwei",
93+
expectedError: fmt.Errorf("failed to parse value: [.7 Gwei]"),
94+
},
95+
"duplicated delimeters": {
96+
value: "3..4 wei",
97+
expectedError: fmt.Errorf("failed to parse value: [3..4 wei]"),
98+
},
99+
"multiple decimals": {
100+
value: "3.4.5 wei",
101+
expectedError: fmt.Errorf("failed to parse value: [3.4.5 wei]"),
102+
},
103+
"invalid thousand separator": {
104+
value: "4 500 gwei",
105+
expectedError: fmt.Errorf("failed to parse value: [4 500 gwei]"),
106+
},
107+
"two values": {
108+
value: "3 wei2wei",
109+
expectedError: fmt.Errorf("invalid unit: wei2wei; please use one of: wei, Gwei, ether"),
110+
},
111+
"two values separated with space": {
112+
value: "3 wei 2wei",
113+
expectedError: fmt.Errorf("failed to parse value: [3 wei 2wei]"),
114+
},
115+
"two values separated with break line": {
116+
value: "3 wei\n2wei",
117+
expectedError: fmt.Errorf("failed to parse value: [3 wei\n2wei]"),
118+
},
119+
"invalid unit: ETH": {
120+
value: "6 ETH",
121+
expectedError: fmt.Errorf("invalid unit: ETH; please use one of: wei, Gwei, ether"),
122+
},
123+
"invalid unit: weinot": {
124+
value: "100 weinot",
125+
expectedError: fmt.Errorf("invalid unit: weinot; please use one of: wei, Gwei, ether"),
126+
},
127+
"invalid unit: notawei": {
128+
value: "100 notawei",
129+
expectedError: fmt.Errorf("invalid unit: notawei; please use one of: wei, Gwei, ether"),
130+
},
131+
"only unit": {
132+
value: "wei",
133+
expectedError: fmt.Errorf("failed to parse value: [wei]"),
134+
},
135+
"invalid number": {
136+
value: "one wei",
137+
expectedError: fmt.Errorf("failed to parse value: [one wei]"),
138+
},
139+
}
140+
for testName, test := range tests {
141+
t.Run(testName, func(t *testing.T) {
142+
143+
e := &Wei{}
144+
err := e.UnmarshalText([]byte(test.value))
145+
if test.expectedError != nil {
146+
if !reflect.DeepEqual(test.expectedError, err) {
147+
t.Errorf(
148+
"invalid error\nexpected: %v\nactual: %v",
149+
test.expectedError,
150+
err,
151+
)
152+
}
153+
} else if err != nil {
154+
t.Errorf("unexpected error: %v", err)
155+
}
156+
157+
if test.expectedResult != nil && test.expectedResult.Cmp(e.Int) != 0 {
158+
t.Errorf(
159+
"invalid value\nexpected: %v\nactual: %v",
160+
test.expectedResult.String(),
161+
e.Int.String(),
162+
)
163+
}
164+
})
165+
}
166+
}

pkg/cmd/cmd.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ var (
5555
// gas price can not be higher than the max gas price value. If the maximum
5656
// allowed gas price is reached, no further resubmission attempts are
5757
// performed. This value can be overwritten in the configuration file.
58-
DefaultMaxGasPrice = big.NewInt(50000000000) // 50 Gwei
58+
DefaultMaxGasPrice = big.NewInt(500000000000) // 500 Gwei
5959
)
6060

6161
// AvailableCommands is the exported list of generated commands that can be

tools/generators/ethereum/command.go.tmpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,8 @@ func initialize{{.Class}}(c *cli.Context) (*contract.{{.Class}}, error) {
220220
if config.MiningCheckInterval != 0 {
221221
checkInterval = time.Duration(config.MiningCheckInterval) * time.Second
222222
}
223-
if config.MaxGasPrice != 0 {
224-
maxGasPrice = new(big.Int).SetUint64(config.MaxGasPrice)
223+
if config.MaxGasPrice != nil {
224+
maxGasPrice = config.MaxGasPrice.Int
225225
}
226226

227227
miningWaiter := ethutil.NewMiningWaiter(client, checkInterval, maxGasPrice)

tools/generators/ethereum/command_template_content.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,8 @@ func initialize{{.Class}}(c *cli.Context) (*contract.{{.Class}}, error) {
223223
if config.MiningCheckInterval != 0 {
224224
checkInterval = time.Duration(config.MiningCheckInterval) * time.Second
225225
}
226-
if config.MaxGasPrice != 0 {
227-
maxGasPrice = new(big.Int).SetUint64(config.MaxGasPrice)
226+
if config.MaxGasPrice != nil {
227+
maxGasPrice = config.MaxGasPrice.Int
228228
}
229229
230230
miningWaiter := ethutil.NewMiningWaiter(client, checkInterval, maxGasPrice)

0 commit comments

Comments
 (0)