diff --git a/contract/state_module.c b/contract/state_module.c index 48e0b1ef8..4a0c6507a 100644 --- a/contract/state_module.c +++ b/contract/state_module.c @@ -23,6 +23,22 @@ static int state_map_delete(lua_State *L); static int state_array_append(lua_State *L); static int state_array_pairs(lua_State *L); +/* +** If this is a sub-element, update the index at position 2 with +** the parent's element key. +*/ +static void update_key_with_parent(lua_State *L, char *parent_key) { + if (parent_key != NULL) { + // concatenate the key at this level with the given index + lua_pushstring(L, parent_key); + lua_pushstring(L, "-"); + lua_pushvalue(L, 2); + lua_concat(L, 3); + // update the index at position 2 + lua_replace(L, 2); + } +} + /* map */ typedef struct { @@ -33,7 +49,7 @@ typedef struct { } state_map_t; static int state_map(lua_State *L) { - int argn = lua_gettop(L); + int nargs = lua_gettop(L); state_map_t *m = lua_newuserdata(L, sizeof(state_map_t)); /* m */ m->id = NULL; @@ -42,7 +58,7 @@ static int state_map(lua_State *L) { if (luaL_isinteger(L, 1)) m->dimension = luaL_checkint(L, 1); - else if (argn == 0) + else if (nargs == 0) m->dimension = 1; else luaL_typerror(L, 1, "integer"); @@ -57,32 +73,46 @@ static int state_map(lua_State *L) { return 1; } -static void state_map_check_index(lua_State *L, state_map_t *m) { - /* m key */ - int key_type = lua_type(L, 2); +/* +** Once a state map is accessed with one type, it can only +** be used with that type (string or number). +** Probably to make it more compatible with Lua tables, +** where m[1] ~= m['1'] +*/ +static void state_map_check_index_type(lua_State *L, state_map_t *m) { + // get the type used for the key on the current access + int key_type = lua_type(L, 2); /* m key */ + // get the type used on the given map int stored_type = m->key_type; - + // maps can be accessed with only string and number if (key_type != LUA_TNUMBER && key_type != LUA_TSTRING) { luaL_error(L, "invalid key type: " LUA_QS ", state.map: " LUA_QS, lua_typename(L, key_type), m->id); } + // if the key type for this map is not loaded yet... if (stored_type == LUA_TNONE) { + // load it from state lua_pushcfunction(L, getItemWithPrefix); /* m key f */ - lua_pushstring(L, m->id); /* m key f id */ - lua_pushstring(L, STATE_VAR_META_TYPE); /* m key f id prefix */ - lua_call(L, 2, 1); /* m key t */ + lua_pushstring(L, m->id); /* m key f id */ + lua_pushstring(L, STATE_VAR_META_TYPE); /* m key f id prefix */ + lua_call(L, 2, 1); /* m key t */ + // is any type stored for this map? if (!lua_isnil(L, -1)) { + // if yes, check the type stored_type = luaL_checkint(L, -1); if (stored_type != LUA_TNUMBER && stored_type != LUA_TSTRING) { luaL_error(L, "invalid stored key type: " LUA_QS ", state.map:", lua_typename(L, stored_type), m->id); } } + // store the type on the map if (vm_is_hardfork(L, 2)) { m->key_type = stored_type; } + // remove it from stack lua_pop(L, 1); } + // make sure the map is accessed with the same type if (stored_type != LUA_TNONE && key_type != stored_type) { luaL_typerror(L, 2, lua_typename(L, stored_type)); } @@ -90,15 +120,15 @@ static void state_map_check_index(lua_State *L, state_map_t *m) { static void state_map_push_key(lua_State *L, state_map_t *m) { lua_pushstring(L, m->id); /* m key value f id */ - lua_pushstring(L, "-"); + lua_pushstring(L, "-"); /* m key value f id '-' */ lua_pushvalue(L, 2); /* m key value f id '-' key */ lua_concat(L, 3); /* m key value f id-key */ } static int state_map_get(lua_State *L) { int key_type = LUA_TNONE; - int arg = lua_gettop(L); - state_map_t *m = luaL_checkudata(L, 1, STATE_MAP_ID); /* m key */ + int nargs = lua_gettop(L); /* map key [blockheight] */ + state_map_t *m = luaL_checkudata(L, 1, STATE_MAP_ID); key_type = lua_type(L, 2); if (key_type == LUA_TSTRING) { @@ -109,7 +139,7 @@ static int state_map_get(lua_State *L) { } } - state_map_check_index(L, m); + state_map_check_index_type(L, m); if (m->dimension > 1) { state_map_t *subm = lua_newuserdata(L, sizeof(state_map_t)); /* m */ @@ -117,37 +147,32 @@ static int state_map_get(lua_State *L) { subm->key_type = m->key_type; subm->dimension = m->dimension - 1; - luaL_getmetatable(L, STATE_MAP_ID); /* m mt */ - lua_setmetatable(L, -2); /* m */ - if (m->key == NULL) { + luaL_getmetatable(L, STATE_MAP_ID); /* m mt */ + lua_setmetatable(L, -2); /* m */ + + if (m->key == NULL) { /* m key */ subm->key = strdup(lua_tostring(L, 2)); } else { - lua_pushstring(L, m->key); /* a key value f id '-' */ - lua_pushstring(L, "-"); /* a key value f id '-' */ - lua_pushvalue(L, 2); /* m key f id-key prefix */ - lua_concat(L, 3); /* m key value f id-key */ + lua_pushstring(L, m->key); /* m key id */ + lua_pushstring(L, "-"); /* m key id '-' */ + lua_pushvalue(L, 2); /* m key id '-' key */ + lua_concat(L, 3); /* m key id-key */ subm->key = strdup(lua_tostring(L, -1)); - lua_pop(L, 1); + lua_pop(L, 1); /* m key */ } return 1; } + // read the value associated with the given key lua_pushcfunction(L, getItemWithPrefix); /* m key f */ - if (m->key != NULL) { - lua_pushstring(L, m->key); - lua_pushstring(L, "-"); - lua_pushvalue(L, 2); - lua_concat(L, 3); - lua_replace(L, 2); - } - + // if this is a sub-map, update the index with the parent's map key + update_key_with_parent(L, m->key); state_map_push_key(L, m); /* m key f id-key */ - if (arg == 3) { - lua_pushvalue(L, 3); + if (nargs == 3) { /* m key h f id-key */ + lua_pushvalue(L, 3); /* m key h f id-key h */ } - - lua_pushstring(L, STATE_VAR_KEY_PREFIX); /* m key value f id-key value prefix */ - lua_call(L, arg, 1); /* m key rv */ + lua_pushstring(L, STATE_VAR_KEY_PREFIX); /* m key f id-key prefix */ + lua_call(L, nargs, 1); /* m key value */ return 1; } @@ -168,28 +193,24 @@ static int state_map_set(lua_State *L) { } } - state_map_check_index(L, m); + state_map_check_index_type(L, m); + // store the data type used for keys on this map if (m->key_type == LUA_TNONE) { - lua_pushcfunction(L, setItemWithPrefix); /* m key f */ - lua_pushstring(L, m->id); /* m key f id */ - lua_pushinteger(L, key_type); /* m key f id type */ - lua_pushstring(L, STATE_VAR_META_TYPE); /* m key f id type prefix */ - lua_call(L, 3, 0); /* m key */ + lua_pushcfunction(L, setItemWithPrefix); /* m key value f */ + lua_pushstring(L, m->id); /* m key value f id */ + lua_pushinteger(L, key_type); /* m key value f id type */ + lua_pushstring(L, STATE_VAR_META_TYPE); /* m key value f id type prefix */ + lua_call(L, 3, 0); /* m key value */ m->key_type = key_type; } luaL_checkany(L, 3); - lua_pushcfunction(L, setItemWithPrefix); /* m key value f */ - - if (m->key != NULL) { - lua_pushstring(L, m->key); - lua_pushstring(L, "-"); - lua_pushvalue(L, 2); - lua_concat(L, 3); - lua_replace(L, 2); - } + // save the value with the given key + lua_pushcfunction(L, setItemWithPrefix); /* m key value f */ + // if this is a sub-map, update the index with the parent's map key + update_key_with_parent(L, m->key); state_map_push_key(L, m); /* m key value f id-key */ lua_pushvalue(L, 3); /* m key value f id-key value */ lua_pushstring(L, STATE_VAR_KEY_PREFIX); /* m key value f id-key value prefix */ @@ -205,16 +226,12 @@ static int state_map_delete(lua_State *L) { luaL_error(L, "not permitted to set intermediate dimension of map"); } - state_map_check_index(L, m); - lua_pushcfunction(L, delItemWithPrefix); /* m key f */ + state_map_check_index_type(L, m); - if (m->key != NULL) { - lua_pushstring(L, m->key); - lua_pushstring(L, "-"); - lua_pushvalue(L, 2); - lua_concat(L, 3); - lua_replace(L, 2); - } + // delete the item with the given key + lua_pushcfunction(L, delItemWithPrefix); /* m key f */ + // if this is a sub-map, update the index with the parent's map key + update_key_with_parent(L, m->key); state_map_push_key(L, m); /* m key f id-key */ lua_pushstring(L, STATE_VAR_KEY_PREFIX); /* m key f id-key prefix */ lua_call(L, 2, 1); /* m key rv */ @@ -292,9 +309,9 @@ static int state_array_len(lua_State *L) { static void state_array_load_len(lua_State *L, state_array_t *arr) { if (!arr->is_fixed && arr->lens == NULL) { lua_pushcfunction(L, getItemWithPrefix); /* a i f */ - lua_pushstring(L, arr->id); /* a i f id */ - lua_pushstring(L, STATE_VAR_META_LEN); /* a i f id prefix */ - lua_call(L, 2, 1); /* a i n */ + lua_pushstring(L, arr->id); /* a i f id */ + lua_pushstring(L, STATE_VAR_META_LEN); /* a i f id prefix */ + lua_call(L, 2, 1); /* a i n */ arr->lens = malloc(sizeof(int32_t)); arr->lens[0] = luaL_optinteger(L, -1, 0); lua_pop(L, 1); @@ -311,15 +328,15 @@ static void state_array_checkarg(lua_State *L, state_array_t *arr) { } static void state_array_push_key(lua_State *L, const char *id) { - lua_pushstring(L, id); /* a key value f id */ - lua_pushstring(L, "-"); /* a key value f id '-' */ - lua_pushvalue(L, 2); /* m key value f id '-' key */ - lua_concat(L, 3); /* m key value f id-key */ + lua_pushstring(L, id); /* a i value f id */ + lua_pushstring(L, "-"); /* a i value f id '-' */ + lua_pushvalue(L, 2); /* a i value f id '-' key */ + lua_concat(L, 3); /* a i value f id-key */ } static int state_array_get(lua_State *L) { state_array_t *arr; - int arg = lua_gettop(L); + int nargs = lua_gettop(L); /* arr index [blockheight] */ int key_type = LUA_TNONE; arr = luaL_checkudata(L, 1, STATE_ARRAY_ID); @@ -339,6 +356,7 @@ static int state_array_get(lua_State *L) { } luaL_typerror(L, 2, "integer"); } + if (arr->dimension > 1) { state_array_t *suba = lua_newuserdata(L, sizeof(state_array_t)); /* m */ suba->id = strdup(arr->id); @@ -348,38 +366,36 @@ static int state_array_get(lua_State *L) { memcpy(suba->lens, arr->lens + 1, sizeof(int32_t) * suba->dimension); luaL_getmetatable(L, STATE_ARRAY_ID); /* m mt */ - lua_setmetatable(L, -2); /* m */ - if (arr->key == NULL) { + lua_setmetatable(L, -2); /* m */ + + if (arr->key == NULL) { /* a i */ suba->key = strdup(lua_tostring(L, 2)); } else { - lua_pushstring(L, arr->key); /* a key value f id '-' */ - lua_pushstring(L, "-"); /* a key value f id '-' */ - lua_pushvalue(L, 2); /* m key f id-key prefix */ - lua_concat(L, 3); /* m key value f id-key */ + lua_pushstring(L, arr->key); /* a i key */ + lua_pushstring(L, "-"); /* a i key '-' */ + lua_pushvalue(L, 2); /* a i key '-' i */ + lua_concat(L, 3); /* a i key-i */ suba->key = strdup(lua_tostring(L, -1)); - lua_pop(L, 1); + lua_pop(L, 1); /* a i */ } return 1; } - if (arg == 3) { - lua_pushvalue(L, 2); + + if (nargs == 3) { + lua_pushvalue(L, 2); //? why? } state_array_checkarg(L, arr); /* a i */ - lua_pushcfunction(L, getItemWithPrefix); /* a i f */ - if (arr->key != NULL) { - lua_pushstring(L, arr->key); - lua_pushstring(L, "-"); - lua_pushvalue(L, 2); - lua_concat(L, 3); - lua_replace(L, 2); - } - state_array_push_key(L, arr->id); /* a i f id-i */ - if (arg == 3) { - lua_pushvalue(L, 3); /* a i s i f id-i s */ - } - lua_pushstring(L, STATE_VAR_KEY_PREFIX); /* a i f id-i prefix */ - lua_call(L, arg, 1); /* a i rv */ + // read the value at the given position + lua_pushcfunction(L, getItemWithPrefix); /* a i f */ + // if this is a sub-array, update the index with the parent's array key + update_key_with_parent(L, arr->key); + state_array_push_key(L, arr->id); /* a i [h] f id-i */ + if (nargs == 3) { /* a i h f id-i */ + lua_pushvalue(L, 3); /* a i h f id-i h */ + } + lua_pushstring(L, STATE_VAR_KEY_PREFIX); /* a i [h] f id-i [h] prefix */ + lua_call(L, nargs, 1); /* a i value */ return 1; } @@ -394,41 +410,48 @@ static int state_array_set(lua_State *L) { state_array_load_len(L, arr); state_array_checkarg(L, arr); /* a i v */ - if (arr->key != NULL) { - lua_pushstring(L, arr->key); - lua_pushstring(L, "-"); - lua_pushvalue(L, 2); - lua_concat(L, 3); - lua_replace(L, 2); - } + + // if this is a sub-array, update the index with the parent's array key + update_key_with_parent(L, arr->key); + + // save the value at the given position lua_pushcfunction(L, setItemWithPrefix); /* a i v f */ state_array_push_key(L, arr->id); /* a i v f id-i */ lua_pushvalue(L, 3); /* a i v f id-i v */ lua_pushstring(L, STATE_VAR_KEY_PREFIX); /* a i v f id-i v prefix */ lua_call(L, 3, 0); /* a i v */ + return 0; } static int state_array_append(lua_State *L) { + // get reference to the array state_array_t *arr = luaL_checkudata(L, 1, STATE_ARRAY_ID); - luaL_checkany(L, 2); + // ensure there is a value of any type + luaL_checkany(L, 2); /* a v */ + // append() function only allowed on variable size arrays if (arr->is_fixed) { luaL_error(L, "the fixed array cannot use " LUA_QL("append") " method"); } + // check for overflow in size if (arr->lens[0] + 1 <= 0) { luaL_error(L, "state.array " LUA_QS " overflow", arr->id); } + // increment the size of the array arr->lens[0]++; + // save the value at the given position lua_pushcfunction(L, state_array_set); /* a v f */ lua_pushvalue(L, 1); /* a v f a */ - lua_pushinteger(L, arr->lens[0]); /* a v f a i */ + lua_pushinteger(L, arr->lens[0]); /* a v f a i */ lua_pushvalue(L, 2); /* a v f a i v */ lua_call(L, 3, 0); - lua_pushcfunction(L, setItemWithPrefix); - lua_pushstring(L, arr->id); - lua_pushinteger(L, arr->lens[0]); - lua_pushstring(L, STATE_VAR_META_LEN); + // save the new array size on the state + lua_pushcfunction(L, setItemWithPrefix); /* a v f */ + lua_pushstring(L, arr->id); /* a v f id */ + lua_pushinteger(L, arr->lens[0]); /* a v f id size */ + lua_pushstring(L, STATE_VAR_META_LEN); /* a v f id size prefix */ lua_call(L, 3, 0); + // nothing to return return 0; } @@ -496,29 +519,29 @@ static int state_value_get(lua_State *L) { } static int state_value_snapget(lua_State *L) { - int arg = lua_gettop(L); + int nargs = lua_gettop(L); state_value_t *v = luaL_checkudata(L, 1, STATE_VALUE_ID); /* v */ lua_pushcfunction(L, getItemWithPrefix); /* v f */ lua_pushstring(L, v->id); /* v f id */ - if (arg == 2) { + if (nargs == 2) { lua_pushvalue(L, 2); } lua_pushstring(L, STATE_VAR_KEY_PREFIX); /* v f id prefix */ - lua_call(L, arg + 1, 1); /* v rv */ + lua_call(L, nargs + 1, 1); /* v rv */ return 1; } static int state_value_set(lua_State *L) { - state_value_t *v = luaL_checkudata(L, 1, STATE_VALUE_ID); /* v */ - luaL_checkany(L, 2); - lua_pushcfunction(L, setItemWithPrefix); /* t f */ + state_value_t *v = luaL_checkudata(L, 1, STATE_VALUE_ID); + luaL_checkany(L, 2); /* s v */ + lua_pushcfunction(L, setItemWithPrefix); /* s v f */ if (v->id == NULL) { luaL_error(L, "invalid state.value: (nil)"); } - lua_pushstring(L, v->id); /* v f id */ - lua_pushvalue(L, 2); /* t f id value */ - lua_pushstring(L, STATE_VAR_KEY_PREFIX); /* v f id value prefix */ - lua_call(L, 3, 0); /* v */ + lua_pushstring(L, v->id); /* s v f id */ + lua_pushvalue(L, 2); /* s v f id value */ + lua_pushstring(L, STATE_VAR_KEY_PREFIX); /* s v f id value prefix */ + lua_call(L, 3, 0); /* s v */ return 0; } diff --git a/contract/system/vote.go b/contract/system/vote.go index 1ea2b29e1..cf5f9e083 100644 --- a/contract/system/vote.go +++ b/contract/system/vote.go @@ -26,7 +26,7 @@ const ( ) var ( - votingCatalog []types.VotingIssue + votingCatalog []types.VotingIssue lastBpCount int defaultVoteKey = []byte(types.OpvoteBP.ID()) ) diff --git a/contract/vm_callback.go b/contract/vm_callback.go index 6187a93ff..a2fa09019 100644 --- a/contract/vm_callback.go +++ b/contract/vm_callback.go @@ -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" @@ -1034,73 +1031,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 diff --git a/contract/vm_callback_test.go b/contract/vm_callback_test.go new file mode 100644 index 000000000..5f5b9d84f --- /dev/null +++ b/contract/vm_callback_test.go @@ -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) + } + } + } + +}