-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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"
- Loading branch information
Showing
6 changed files
with
250 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package ethereum | ||
|
||
import ( | ||
"fmt" | ||
"math/big" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/ethereum/go-ethereum/params" | ||
) | ||
|
||
// Wei is a custom type to handle Ether value parsing in configuration files | ||
// using BurntSushi/toml package. It supports wei, Gwei and ether units. The | ||
// Ether value is kept as `wei` and `wei` is the default unit. | ||
// The value can be provided in the text file as e.g.: `1 wei`, `200 Gwei` or | ||
// `0.5 ether`. | ||
type Wei struct { | ||
*big.Int | ||
} | ||
|
||
// The most common units for ether values. | ||
const ( | ||
wei unit = iota | ||
gwei | ||
ether | ||
) | ||
|
||
// unit represents Ether value unit. | ||
type unit int | ||
|
||
func (u unit) String() string { | ||
return [...]string{"wei", "Gwei", "ether"}[u] | ||
} | ||
|
||
// UnmarshalText is a function used to parse a value of Ethers. | ||
func (e *Wei) UnmarshalText(text []byte) error { | ||
re := regexp.MustCompile(`^(\d+[\.]?[\d]*)[ ]?([\w]*)$`) | ||
matched := re.FindSubmatch(text) | ||
|
||
if len(matched) != 3 { | ||
return fmt.Errorf("failed to parse value: [%s]", text) | ||
} | ||
|
||
number, ok := new(big.Float).SetString(string(matched[1])) | ||
if !ok { | ||
return fmt.Errorf( | ||
"failed to set float value from string [%s]", | ||
string(matched[1]), | ||
) | ||
} | ||
|
||
unit := matched[2] | ||
if len(unit) == 0 { | ||
unit = []byte("wei") | ||
} | ||
|
||
switch strings.ToLower(string(unit)) { | ||
case strings.ToLower(ether.String()): | ||
number.Mul(number, big.NewFloat(params.Ether)) | ||
e.Int, _ = number.Int(nil) | ||
case strings.ToLower(gwei.String()): | ||
number.Mul(number, big.NewFloat(params.GWei)) | ||
e.Int, _ = number.Int(nil) | ||
case strings.ToLower(wei.String()): | ||
number.Mul(number, big.NewFloat(params.Wei)) | ||
e.Int, _ = number.Int(nil) | ||
default: | ||
return fmt.Errorf( | ||
"invalid unit: %s; please use one of: wei, Gwei, ether", | ||
unit, | ||
) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
package ethereum | ||
|
||
import ( | ||
"fmt" | ||
"math/big" | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
func TestUnmarshalText(t *testing.T) { | ||
int5000ether, _ := new(big.Int).SetString("5000000000000000000000", 10) | ||
|
||
var tests = map[string]struct { | ||
value string | ||
expectedResult *big.Int | ||
expectedError error | ||
}{ | ||
"decimal value": { | ||
value: "0.9", | ||
expectedResult: big.NewInt(0), | ||
}, | ||
"lowest value": { | ||
value: "1", | ||
expectedResult: big.NewInt(1), | ||
}, | ||
"missing unit": { | ||
value: "702", | ||
expectedResult: big.NewInt(702), | ||
}, | ||
"unit: wei": { | ||
value: "4 wei", | ||
expectedResult: big.NewInt(4), | ||
}, | ||
"unit: gwei": { | ||
value: "30 gwei", | ||
expectedResult: big.NewInt(30000000000), | ||
}, | ||
"unit: ether": { | ||
value: "2 ether", | ||
expectedResult: big.NewInt(2000000000000000000), | ||
}, | ||
"unit: mixed case": { | ||
value: "5 GWei", | ||
expectedResult: big.NewInt(5000000000), | ||
}, | ||
"decimal wei": { | ||
value: "2.9 wei", | ||
expectedResult: big.NewInt(2), | ||
}, | ||
"decimal ether": { | ||
value: "0.8 ether", | ||
expectedResult: big.NewInt(800000000000000000), | ||
}, | ||
"multiple decimal digits": { | ||
value: "5.6789 Gwei", | ||
expectedResult: big.NewInt(5678900000), | ||
}, | ||
"missing decimal digit": { | ||
value: "6. Gwei", | ||
expectedResult: big.NewInt(6000000000), | ||
}, | ||
"no space": { | ||
value: "9ether", | ||
expectedResult: big.NewInt(9000000000000000000), | ||
}, | ||
"int overflow amount": { | ||
value: "5000000000000000000000", | ||
expectedResult: int5000ether, | ||
}, | ||
"int overflow amount after conversion": { | ||
value: "5000 ether", | ||
expectedResult: int5000ether, | ||
}, | ||
"double space": { | ||
value: "100 Gwei", | ||
expectedError: fmt.Errorf("failed to parse value: [100 Gwei]"), | ||
}, | ||
"leading space": { | ||
value: " 3 wei", | ||
expectedError: fmt.Errorf("failed to parse value: [ 3 wei]"), | ||
}, | ||
"trailing space": { | ||
value: "3 wei ", | ||
expectedError: fmt.Errorf("failed to parse value: [3 wei ]"), | ||
}, | ||
|
||
"invalid comma delimeter": { | ||
value: "3,5 ether", | ||
expectedError: fmt.Errorf("failed to parse value: [3,5 ether]"), | ||
}, | ||
"only decimal number": { | ||
value: ".7 Gwei", | ||
expectedError: fmt.Errorf("failed to parse value: [.7 Gwei]"), | ||
}, | ||
"duplicated delimeters": { | ||
value: "3..4 wei", | ||
expectedError: fmt.Errorf("failed to parse value: [3..4 wei]"), | ||
}, | ||
"multiple decimals": { | ||
value: "3.4.5 wei", | ||
expectedError: fmt.Errorf("failed to parse value: [3.4.5 wei]"), | ||
}, | ||
"invalid thousand separator": { | ||
value: "4 500 gwei", | ||
expectedError: fmt.Errorf("failed to parse value: [4 500 gwei]"), | ||
}, | ||
"two values": { | ||
value: "3 wei2wei", | ||
expectedError: fmt.Errorf("invalid unit: wei2wei; please use one of: wei, Gwei, ether"), | ||
}, | ||
"two values separated with space": { | ||
value: "3 wei 2wei", | ||
expectedError: fmt.Errorf("failed to parse value: [3 wei 2wei]"), | ||
}, | ||
"two values separated with break line": { | ||
value: "3 wei\n2wei", | ||
expectedError: fmt.Errorf("failed to parse value: [3 wei\n2wei]"), | ||
}, | ||
"invalid unit: ETH": { | ||
value: "6 ETH", | ||
expectedError: fmt.Errorf("invalid unit: ETH; please use one of: wei, Gwei, ether"), | ||
}, | ||
"invalid unit: weinot": { | ||
value: "100 weinot", | ||
expectedError: fmt.Errorf("invalid unit: weinot; please use one of: wei, Gwei, ether"), | ||
}, | ||
"invalid unit: notawei": { | ||
value: "100 notawei", | ||
expectedError: fmt.Errorf("invalid unit: notawei; please use one of: wei, Gwei, ether"), | ||
}, | ||
"only unit": { | ||
value: "wei", | ||
expectedError: fmt.Errorf("failed to parse value: [wei]"), | ||
}, | ||
"invalid number": { | ||
value: "one wei", | ||
expectedError: fmt.Errorf("failed to parse value: [one wei]"), | ||
}, | ||
} | ||
for testName, test := range tests { | ||
t.Run(testName, func(t *testing.T) { | ||
|
||
e := &Wei{} | ||
err := e.UnmarshalText([]byte(test.value)) | ||
if test.expectedError != nil { | ||
if !reflect.DeepEqual(test.expectedError, err) { | ||
t.Errorf( | ||
"invalid error\nexpected: %v\nactual: %v", | ||
test.expectedError, | ||
err, | ||
) | ||
} | ||
} else if err != nil { | ||
t.Errorf("unexpected error: %v", err) | ||
} | ||
|
||
if test.expectedResult != nil && test.expectedResult.Cmp(e.Int) != 0 { | ||
t.Errorf( | ||
"invalid value\nexpected: %v\nactual: %v", | ||
test.expectedResult.String(), | ||
e.Int.String(), | ||
) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters