Skip to content

Commit

Permalink
Merge pull request #297 from aergoio/topic/parse-amount
Browse files Browse the repository at this point in the history
enhance the parsing of amount
  • Loading branch information
kroggen authored Nov 1, 2023
2 parents 509f779 + 9ceb03f commit 6797a9f
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 54 deletions.
2 changes: 1 addition & 1 deletion contract/system/vote.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const (
)

var (
votingCatalog []types.VotingIssue
votingCatalog []types.VotingIssue
lastBpCount int
defaultVoteKey = []byte(types.OpvoteBP.ID())
)
Expand Down
113 changes: 60 additions & 53 deletions contract/vm_callback.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,12 @@ import (
"encoding/hex"
"errors"
"fmt"
"index/suffixarray"
"math/big"
"regexp"
"strconv"
"strings"
"unsafe"

"github.com/aergoio/aergo-lib/log"

"github.com/aergoio/aergo/v2/cmd/aergoluac/util"
"github.com/aergoio/aergo/v2/contract/name"
"github.com/aergoio/aergo/v2/contract/system"
Expand Down Expand Up @@ -1033,73 +1030,83 @@ func luaCryptoKeccak256(data unsafe.Pointer, dataLen C.int) (unsafe.Pointer, int
}
}

// transformAmount processes the input string to calculate the total amount,
// taking into account the different units ("aergo", "gaer", "aer")
func transformAmount(amountStr string) (*big.Int, error) {
var ret *big.Int
var prev int
if len(amountStr) == 0 {
return zeroBig, nil
}
index := suffixarray.New([]byte(amountStr))
r := regexp.MustCompile("(?i)aergo|gaer|aer")

res := index.FindAllIndex(r, -1)
for _, pair := range res {
amountBig, _ := new(big.Int).SetString(strings.TrimSpace(amountStr[prev:pair[0]]), 10)
if amountBig == nil {
return nil, errors.New("converting error for BigNum: " + amountStr[prev:])
}
cmp := amountBig.Cmp(zeroBig)
if cmp < 0 {
return nil, errors.New("negative amount not allowed")
} else if cmp == 0 {
prev = pair[1]
continue
}
switch pair[1] - pair[0] {
case 3:
case 4:
amountBig = new(big.Int).Mul(amountBig, mulGaer)
case 5:
amountBig = new(big.Int).Mul(amountBig, mulAergo)
}
if ret != nil {
ret = new(big.Int).Add(ret, amountBig)
} else {
ret = amountBig
}
prev = pair[1]
}
totalAmount := new(big.Int)
remainingStr := amountStr

// Define the units and corresponding multipliers
for _, data := range []struct {
unit string
multiplier *big.Int
}{
{"aergo", mulAergo},
{"gaer", mulGaer},
{"aer", zeroBig},
} {
idx := strings.Index(strings.ToLower(remainingStr), data.unit)
if idx != -1 {
// Extract the part before the unit
subStr := remainingStr[:idx]

// Parse and convert the amount
partialAmount, err := parseAndConvert(subStr, data.unit, data.multiplier, amountStr)
if err != nil {
return nil, err
}

if prev >= len(amountStr) {
if ret != nil {
return ret, nil
} else {
return zeroBig, nil
// Add to the total amount
totalAmount.Add(totalAmount, partialAmount)

// Adjust the remaining string to process
remainingStr = remainingStr[idx+len(data.unit):]
}
}
num := strings.TrimSpace(amountStr[prev:])
if len(num) == 0 {
if ret != nil {
return ret, nil
} else {
return zeroBig, nil

// Process the rest of the string, if there is some
if len(remainingStr) > 0 {
partialAmount, err := parseAndConvert(remainingStr, "", zeroBig, amountStr)
if err != nil {
return nil, err
}

// Add to the total amount
totalAmount.Add(totalAmount, partialAmount)
}

amountBig, _ := new(big.Int).SetString(num, 10)
return totalAmount, nil
}

// parseAndConvert is a helper function to parse the substring as a big integer
// and apply the necessary multiplier based on the unit.
func parseAndConvert(subStr, unit string, mulUnit *big.Int, amountStr string) (*big.Int, error) {
trimmedStr := strings.TrimSpace(subStr)

if amountBig == nil {
return nil, errors.New("converting error for Integer: " + amountStr[prev:])
// Convert the trimmed string to a big integer
amountBig, valid := new(big.Int).SetString(trimmedStr, 10)
if !valid {
// Emits a backwards compatible error message
// the same as: dataType := len(unit) > 0 ? "BigNum" : "Integer"
dataType := map[bool]string{true: "BigNum", false: "Integer"}[len(unit) > 0]
return nil, errors.New("converting error for " + dataType + ": " + strings.TrimSpace(amountStr))
}

// Check for negative amounts
if amountBig.Cmp(zeroBig) < 0 {
return nil, errors.New("negative amount not allowed")
}
if ret != nil {
ret = new(big.Int).Add(ret, amountBig)
} else {
ret = amountBig

// Apply multiplier based on unit
if mulUnit != zeroBig {
amountBig.Mul(amountBig, mulUnit)
}
return ret, nil

return amountBig, nil
}

//export luaDeployContract
Expand Down
138 changes: 138 additions & 0 deletions contract/vm_callback_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package contract

import (
"errors"
"math/big"
"testing"

"github.com/aergoio/aergo/v2/types"
"github.com/stretchr/testify/assert"
)

func bigIntFromString(str string) *big.Int {
bigInt, success := new(big.Int).SetString(str, 10)
if !success {
panic("bigIntFromString: invalid number: " + str)
}
return bigInt
}

func TestTransformAmount(t *testing.T) {
// Define the test cases
tests := []struct {
amountStr string
expectedAmount *big.Int
expectedError error
}{
// Empty Input String
{"", big.NewInt(0), nil},
// Valid Amount without Unit
{"1", big.NewInt(1), nil},
{"10", big.NewInt(10), nil},
{"123", big.NewInt(123), nil},
{"123000000", big.NewInt(123000000), nil},
// Valid Amount with Unit
{"100aergo", types.NewAmount(100, types.Aergo), nil},
{"100 aergo", types.NewAmount(100, types.Aergo), nil},
{"123gaer", types.NewAmount(123, types.Gaer), nil},
{"123 gaer", types.NewAmount(123, types.Gaer), nil},
{"123aer", types.NewAmount(123, types.Aer), nil},
{"123 aer", types.NewAmount(123, types.Aer), nil},
// Multipart Amount
{"100aergo 200gaer", bigIntFromString("100000000200000000000"), nil},
{"100 aergo 123 gaer", bigIntFromString("100000000123000000000"), nil},
{"123aergo 456aer", bigIntFromString("123000000000000000456"), nil},
{"123 aergo 456 aer", bigIntFromString("123000000000000000456"), nil},
{"123aergo 456gaer 789aer", bigIntFromString("123000000456000000789"), nil},
{"123 aergo 456 gaer 789 aer", bigIntFromString("123000000456000000789"), nil},
// Invalid Order
{"789aer 456gaer 123aergo", nil, errors.New("converting error for BigNum: 789aer 456gaer 123aergo")},
{"789 aer 456 gaer 123 aergo", nil, errors.New("converting error for BigNum: 789 aer 456 gaer 123 aergo")},
{"789aer 123aergo 456gaer", nil, errors.New("converting error for BigNum: 789aer 123aergo 456gaer")},
{"789 aer 123 aergo 456 gaer", nil, errors.New("converting error for BigNum: 789 aer 123 aergo 456 gaer")},
{"456gaer 789aer 123aergo", nil, errors.New("converting error for BigNum: 456gaer 789aer 123aergo")},
{"123aergo 789aer 456gaer", nil, errors.New("converting error for BigNum: 123aergo 789aer 456gaer")},
// Repeated Units
{"123aergo 456aergo", nil, errors.New("converting error for Integer: 123aergo 456aergo")},
{"123gaer 456gaer", nil, errors.New("converting error for BigNum: 123gaer 456gaer")},
{"123aer 456aer", nil, errors.New("converting error for Integer: 123aer 456aer")},
{"123 aergo 456 aergo", nil, errors.New("converting error for Integer: 123 aergo 456 aergo")},
{"123 gaer 456 gaer", nil, errors.New("converting error for BigNum: 123 gaer 456 gaer")},
{"123 aer 456 aer", nil, errors.New("converting error for Integer: 123 aer 456 aer")},
{"123aergo 456aergo 789aer", nil, errors.New("converting error for Integer: 123aergo 456aergo 789aer")},
{"123aergo 456aergo 789gaer", nil, errors.New("converting error for BigNum: 123aergo 456aergo 789gaer")},
{"123aergo 456gaer 789gaer", nil, errors.New("converting error for BigNum: 123aergo 456gaer 789gaer")},
{"123aergo 456aer 789aer", nil, errors.New("converting error for Integer: 123aergo 456aer 789aer")},
{"123 aergo 456 aergo 789 aer", nil, errors.New("converting error for Integer: 123 aergo 456 aergo 789 aer")},
{"123 aergo 456 aergo 789 gaer", nil, errors.New("converting error for BigNum: 123 aergo 456 aergo 789 gaer")},
{"123 aergo 456 gaer 789 gaer", nil, errors.New("converting error for BigNum: 123 aergo 456 gaer 789 gaer")},
{"123 aergo 456 aer 789 aer", nil, errors.New("converting error for Integer: 123 aergo 456 aer 789 aer")},
// Invalid Amount String
{"notanumber", nil, errors.New("converting error for Integer: notanumber")},
{"e123", nil, errors.New("converting error for Integer: e123")},
{"123e", nil, errors.New("converting error for Integer: 123e")},
{"123 456", nil, errors.New("converting error for Integer: 123 456")},
// Negative Amount
{"-100", nil, errors.New("negative amount not allowed")},
{"-100aergo", nil, errors.New("negative amount not allowed")},
{"-100 aergo", nil, errors.New("negative amount not allowed")},
{"-100 aergo", nil, errors.New("negative amount not allowed")},
{"-100aer", nil, errors.New("negative amount not allowed")},
{"-100 aer", nil, errors.New("negative amount not allowed")},
{"-100 aer", nil, errors.New("negative amount not allowed")},
// Large Number
{"99999999999999999999999999", bigIntFromString("99999999999999999999999999"), nil},
// Zero Value
{"0", big.NewInt(0), nil},
{"0aergo", big.NewInt(0), nil},
{"0 aergo", big.NewInt(0), nil},
{"0gaer", big.NewInt(0), nil},
{"0 gaer", big.NewInt(0), nil},
{"0aer", big.NewInt(0), nil},
{"0 aer", big.NewInt(0), nil},
// Only Unit
{"aergo", nil, errors.New("converting error for BigNum: aergo")},
{"gaer", nil, errors.New("converting error for BigNum: gaer")},
{"aer", nil, errors.New("converting error for BigNum: aer")},
// Invalid Content
{"100 invalid 200", nil, errors.New("converting error for Integer: 100 invalid 200")},
{"invalid 200", nil, errors.New("converting error for Integer: invalid 200")},
{"100 invalid", nil, errors.New("converting error for Integer: 100 invalid")},
// Non-Integer Values
{"123.456", nil, errors.New("converting error for Integer: 123.456")},
{"123.456 aergo", nil, errors.New("converting error for BigNum: 123.456 aergo")},
{".1", nil, errors.New("converting error for Integer: .1")},
{".1aergo", nil, errors.New("converting error for BigNum: .1aergo")},
{".1 aergo", nil, errors.New("converting error for BigNum: .1 aergo")},
{".10", nil, errors.New("converting error for Integer: .10")},
// Exponents
{"1e+18", nil, errors.New("converting error for Integer: 1e+18")},
{"2e18", nil, errors.New("converting error for Integer: 2e18")},
{"3e08", nil, errors.New("converting error for Integer: 3e08")},
{"1e+18 aer", nil, errors.New("converting error for BigNum: 1e+18 aer")},
{"2e+18 aer", nil, errors.New("converting error for BigNum: 2e+18 aer")},
{"3e18 aer", nil, errors.New("converting error for BigNum: 3e18 aer")},
{"1e+18aer", nil, errors.New("converting error for BigNum: 1e+18aer")},
{"2e+18aer", nil, errors.New("converting error for BigNum: 2e+18aer")},
{"3e18aer", nil, errors.New("converting error for BigNum: 3e18aer")},
{"3e+5 aergo", nil, errors.New("converting error for BigNum: 3e+5 aergo")},
{"3e5 aergo", nil, errors.New("converting error for BigNum: 3e5 aergo")},
{"3e05 aergo", nil, errors.New("converting error for BigNum: 3e05 aergo")},
{"5e+3aergo", nil, errors.New("converting error for BigNum: 5e+3aergo")},
}

for _, tt := range tests {
result, err := transformAmount(tt.amountStr)

if tt.expectedError != nil {
if assert.Error(t, err, "Expected error: %s", tt.expectedError.Error()) {
assert.Equal(t, tt.expectedError.Error(), err.Error())
}
} else {
if assert.NoError(t, err) && tt.expectedAmount != nil {
assert.Equal(t, tt.expectedAmount, result)
}
}
}

}

0 comments on commit 6797a9f

Please sign in to comment.