diff --git a/aergo-protobuf b/aergo-protobuf index 0ebd3322c..f0b2b8a6a 160000 --- a/aergo-protobuf +++ b/aergo-protobuf @@ -1 +1 @@ -Subproject commit 0ebd3322cd33af95a70d24590d7e3a488ac8f01e +Subproject commit f0b2b8a6a9347c5b9c44c5bb9e2a30f13a47235b diff --git a/blacklist/blacklist.go b/blacklist/blacklist.go new file mode 100644 index 000000000..373b608d2 --- /dev/null +++ b/blacklist/blacklist.go @@ -0,0 +1,59 @@ +package blacklist + +import ( + "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/internal/common" + "github.com/aergoio/aergo/v2/internal/enc/hex" +) + +type Blacklist struct { + sourcelist []string // account address (b58 encoded like Am...) or id (32 bytes in hex = 64 bytes) + blocked map[string]bool // all above converted to account id (32 bytes) +} + +var globalBlacklist *Blacklist + +// Initialize sets up the blacklist with the given addresses. +// This function should be called only once at the start. +func Initialize(addresses []string) { + conf := &Blacklist{} + conf.sourcelist = make([]string, len(addresses)) + copy(conf.sourcelist, addresses) + conf.blocked = make(map[string]bool) + for _, v := range addresses { + key, err := toKey(v) + if err == nil { + conf.blocked[key] = true + } else { + // Handle invalid address, log or take other actions as needed + } + } + globalBlacklist = conf +} + +func Check(address string) bool { + if globalBlacklist == nil { + return false + } + key, err := toKey(address) + if err != nil { + return false + } + return globalBlacklist.blocked[key] +} + +func toKey(address string) (string, error) { + var key []byte + var err error + if len(address) == 64 { + key, err = hex.Decode(address) + } else { + var addr []byte + addr, err = types.DecodeAddress(address) + if err != nil { + return "", err + } + key = common.Hasher(addr) + } + return string(key), err +} diff --git a/cmd/aergocli/cmd/contract.go b/cmd/aergocli/cmd/contract.go index 9e843a818..fb949583f 100644 --- a/cmd/aergocli/cmd/contract.go +++ b/cmd/aergocli/cmd/contract.go @@ -71,17 +71,17 @@ func init() { contractCmd.PersistentFlags().Uint64VarP(&gas, "gaslimit", "g", 0, "Gas limit") deployCmd := &cobra.Command{ - Use: `deploy [flags] --payload 'payload string' [args] - aergocli contract deploy [flags] [args] + Use: `deploy [flags] [args] + aergocli contract deploy [flags] --payload 'payload string' [args] - You can pass constructor arguments by passing a JSON string as the optional final parameter, e.g. "[1, 2, 3]".`, - Short: "Deploy a compiled contract to the server", - Args: nArgs([]int{1, 2, 3, 4}), + You can pass arguments to the constructor() function by passing a JSON string as the optional final parameter, e.g. '[1, "test"]'`, + Short: "Deploy a contract to the server", + Args: nArgs([]int{1, 2, 3}), RunE: runDeployCmd, DisableFlagsInUseLine: true, } deployCmd.PersistentFlags().Uint64Var(&nonce, "nonce", 0, "manually set a nonce (default: set nonce automatically)") - deployCmd.PersistentFlags().StringVar(&data, "payload", "", "result of compiling a contract") + deployCmd.PersistentFlags().StringVar(&data, "payload", "", "result of compiling a contract with aergoluac") deployCmd.PersistentFlags().StringVar(&amount, "amount", "0", "amount of token to send with deployment, in aer") deployCmd.PersistentFlags().StringVarP(&contractID, "redeploy", "r", "", "redeploy the contract") deployCmd.Flags().StringVar(&pw, "password", "", "password (optional, will be asked on the terminal if not given)") @@ -145,20 +145,6 @@ func init() { rootCmd.AddCommand(contractCmd) } -func isHexString(s string) bool { - // check is the input has even number of characters - if len(s)%2 != 0 { - return false - } - // check if the input contains only hex characters - for _, c := range s { - if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) { - return false - } - } - return true -} - func runDeployCmd(cmd *cobra.Command, args []string) error { var err error var code []byte @@ -179,9 +165,18 @@ func runDeployCmd(cmd *cobra.Command, args []string) error { nonce = state.GetNonce() + 1 } + chainInfo, err := client.GetChainInfo(context.Background(), &types.Empty{}) + if err != nil { + return fmt.Errorf("could not retrieve chain info: %v", err.Error()) + } + var payload []byte if len(data) == 0 { - if len(args) < 3 { + if chainInfo.Id.Version < 4 { + cmd.SilenceUsage = false + return errors.New("for old hardforks use aergoluac and --payload method instead") + } + if len(args) < 2 { cmd.SilenceUsage = false return errors.New("not enough arguments") } @@ -189,21 +184,20 @@ func runDeployCmd(cmd *cobra.Command, args []string) error { if err != nil { return fmt.Errorf("failed to read code file: %v", err.Error()) } - var abi []byte - abi, err = os.ReadFile(args[2]) - if err != nil { - return fmt.Errorf("failed to read abi file: %v", err.Error()) - } - if len(args) == 4 { + if len(args) == 3 { var ci types.CallInfo - err = json.Unmarshal([]byte(args[3]), &ci.Args) + err = json.Unmarshal([]byte(args[2]), &ci.Args) if err != nil { - return fmt.Errorf("failed to parse JSON: %v", err.Error()) + return fmt.Errorf("failed to parse arguments (JSON): %v", err.Error()) } - deployArgs = []byte(args[3]) + deployArgs = []byte(args[2]) } - payload = luac.NewLuaCodePayload(luac.NewLuaCode(code, abi), deployArgs) + payload = luac.NewLuaCodePayload(luac.LuaCode(code), deployArgs) } else { + if chainInfo.Id.Version >= 4 { + cmd.SilenceUsage = false + return errors.New("this chain only accepts deploy in plain source code\nuse the other method instead") + } if len(args) == 2 { var ci types.CallInfo err = json.Unmarshal([]byte(args[1]), &ci.Args) @@ -213,7 +207,11 @@ func runDeployCmd(cmd *cobra.Command, args []string) error { deployArgs = []byte(args[1]) } // check if the data is in hex format - if isHexString(data) { + if hex.IsHexString(data) { + if deployArgs != nil { + cmd.SilenceUsage = false + return errors.New("the call arguments are expected to be already on the hex data") + } // the data is expected to be copied from aergoscan view of // the transaction that deployed the contract payload, err = hex.Decode(data) diff --git a/cmd/aergoluac/main.go b/cmd/aergoluac/main.go index d305fd093..1c4c120b3 100644 --- a/cmd/aergoluac/main.go +++ b/cmd/aergoluac/main.go @@ -17,6 +17,7 @@ var ( rootCmd *cobra.Command abiFile string payload bool + decode bool version bool ) @@ -24,7 +25,7 @@ var githash = "No git hash provided" func init() { rootCmd = &cobra.Command{ - Use: "aergoluac --payload srcfile\n aergoluac --abi abifile srcfile bcfile", + Use: "aergoluac --payload srcfile\n aergoluac srcfile bcfile\n aergoluac --abi abifile srcfile bcfile\n aergoluac --decode payloadfile", Short: "Compile a lua contract", Long: "Compile a lua contract. This command makes a bytecode file and a ABI file or prints a payload data.", RunE: func(cmd *cobra.Command, args []string) error { @@ -34,7 +35,14 @@ func init() { cmd.Printf("Aergoluac %s\n", githash) return nil } - if payload { + + if decode { + if len(args) == 0 { + err = util.DecodeFromStdin() + } else { + err = util.DecodeFromFile(args[0]) + } + } else if payload { if len(args) == 0 { err = util.DumpFromStdin() } else { @@ -46,11 +54,13 @@ func init() { } err = util.CompileFromFile(args[0], args[1], abiFile) } + return err }, } rootCmd.PersistentFlags().StringVarP(&abiFile, "abi", "a", "", "abi filename") rootCmd.PersistentFlags().BoolVar(&payload, "payload", false, "print the compilation result consisting of bytecode and abi") + rootCmd.PersistentFlags().BoolVar(&decode, "decode", false, "extract raw bytecode and abi from payload (hex or base58)") rootCmd.PersistentFlags().BoolVar(&version, "version", false, "print the version number of aergoluac") } diff --git a/cmd/aergoluac/util/luac_util.go b/cmd/aergoluac/util/luac_util.go index 20b038260..9fc9daa89 100644 --- a/cmd/aergoluac/util/luac_util.go +++ b/cmd/aergoluac/util/luac_util.go @@ -20,6 +20,8 @@ import ( "runtime" "unsafe" + "github.com/aergoio/aergo/v2/internal/enc/hex" + "github.com/aergoio/aergo/v2/internal/enc/base58" "github.com/aergoio/aergo/v2/cmd/aergoluac/encoding" ) @@ -130,6 +132,97 @@ func dumpToBytes(L *C.lua_State) LuaCode { return NewLuaCode(C.GoBytes(unsafe.Pointer(c), C.int(lc)), C.GoBytes(unsafe.Pointer(a), C.int(la))) } +func Decode(srcFileName string, payload string) error { + var decoded []byte + var err error + + // check if the payload is in hex format + if hex.IsHexString(payload) { + // the data is expected to be copied from aergoscan view of + // the transaction that deployed the contract + decoded, err = hex.Decode(payload) + } else { + // the data is the output of aergoluac + decoded, err = encoding.DecodeCode(payload) + if err != nil { + // the data is extracted from JSON transaction from aergocli + decoded, err = base58.Decode(payload) + } + } + if err != nil { + return fmt.Errorf("failed to decode payload 1: %v", err.Error()) + } + + data := LuaCodePayload(decoded) + _, err = data.IsValidFormat() + if err != nil { + return fmt.Errorf("failed to decode payload 2: %v", err.Error()) + } + + contract := data.Code() + if !contract.IsValidFormat() { + // the data is the output of aergoluac, so it does not contain deploy arguments + contract = LuaCode(decoded) + data = NewLuaCodePayload(contract, []byte{}) + } + + err = os.WriteFile(srcFileName + "-bytecode", contract.ByteCode(), 0644); + if err != nil { + return fmt.Errorf("failed to write bytecode file: %v", err.Error()) + } + + err = os.WriteFile(srcFileName + "-abi", contract.ABI(), 0644); + if err != nil { + return fmt.Errorf("failed to write ABI file: %v", err.Error()) + } + + var deployArgs []byte + if data.HasArgs() { + deployArgs = data.Args() + } + err = os.WriteFile(srcFileName + "-deploy-arguments", deployArgs, 0644); + if err != nil { + return fmt.Errorf("failed to write deploy-arguments file: %v", err.Error()) + } + + fmt.Println("done.") + return nil +} + +func DecodeFromFile(srcFileName string) error { + payload, err := os.ReadFile(srcFileName) + if err != nil { + return fmt.Errorf("failed to read payload file: %v", err.Error()) + } + return Decode(srcFileName, string(payload)) +} + +func DecodeFromStdin() error { + fi, err := os.Stdin.Stat() + if err != nil { + return err + } + var buf []byte + if (fi.Mode() & os.ModeCharDevice) == 0 { + buf, err = ioutil.ReadAll(os.Stdin) + if err != nil { + return err + } + } else { + var bBuf bytes.Buffer + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + bBuf.WriteString(scanner.Text() + "\n") + } + if err = scanner.Err(); err != nil { + return err + } + buf = bBuf.Bytes() + } + return Decode("contract", string(buf)) +} + + type LuaCode []byte const byteCodeLenLen = 4 diff --git a/cmd/brick/context/util.go b/cmd/brick/context/util.go index dc48b9827..badf16266 100644 --- a/cmd/brick/context/util.go +++ b/cmd/brick/context/util.go @@ -18,6 +18,55 @@ func ParseFirstWord(input string) (string, string) { return strings.ToLower(splitedStr[0]), strings.Join(splitedStr[1:], " ") } +func ParseDecimalAmount(str string, digits int) string { + + // if there is no 'aergo' unit, just return the amount str + if !strings.HasSuffix(strings.ToLower(str), "aergo") { + return str + } + + // remove the 'aergo' unit + str = str[:len(str)-5] + // trim trailing spaces + str = strings.TrimRight(str, " ") + + // get the position of the decimal point + idx := strings.Index(str, ".") + + // if not found, just add the leading zeros + if idx == -1 { + return str + strings.Repeat("0", digits) + } + + // Get the integer and decimal parts + p1 := str[0:idx] + p2 := str[idx+1:] + + // Check for another decimal point + if strings.Index(p2, ".") != -1 { + return "error" + } + + // Compute the amount of zero digits to add + to_add := digits - len(p2) + if to_add > 0 { + p2 = p2 + strings.Repeat("0", to_add) + } else if to_add < 0 { + // Do not truncate decimal amounts + return "error" + } + + // Join the integer and decimal parts + str = p1 + p2 + + // Remove leading zeros + str = strings.TrimLeft(str, "0") + if str == "" { + str = "0" + } + return str +} + type Chunk struct { Accent bool Text string diff --git a/cmd/brick/exec/callContract.go b/cmd/brick/exec/callContract.go index b44ce1ee3..b6efd92e1 100644 --- a/cmd/brick/exec/callContract.go +++ b/cmd/brick/exec/callContract.go @@ -53,7 +53,8 @@ func (c *callContract) parse(args string) (string, *big.Int, string, string, str return "", nil, "", "", "", "", "", fmt.Errorf("need at least 4 arguments. usage: %s", c.Usage()) } - amount, success := new(big.Int).SetString(splitArgs[1].Text, 10) + amountStr := context.ParseDecimalAmount(splitArgs[1].Text, 18) + amount, success := new(big.Int).SetString(amountStr, 10) if success == false { return "", nil, "", "", "", "", "", fmt.Errorf("fail to parse number %s", splitArgs[1].Text) } diff --git a/cmd/brick/exec/debug.go b/cmd/brick/exec/debug.go index b7b9c2537..a88d00be3 100644 --- a/cmd/brick/exec/debug.go +++ b/cmd/brick/exec/debug.go @@ -61,7 +61,7 @@ func (c *setb) parse(args string) (uint64, string, error) { line, err := strconv.ParseUint(splitArgs[0].Text, 10, 64) if err != nil { - return 0, "", fmt.Errorf("fail to parse number %s: %s", splitArgs[1].Text, err.Error()) + return 0, "", fmt.Errorf("fail to parse number %s: %s", splitArgs[0].Text, err.Error()) } contractIDHex := contract.PlainStrToHexAddr(splitArgs[1].Text) @@ -119,7 +119,7 @@ func (c *delb) parse(args string) (uint64, string, error) { line, err := strconv.ParseUint(splitArgs[0].Text, 10, 64) if err != nil { - return 0, "", fmt.Errorf("fail to parse number %s: %s", splitArgs[1].Text, err.Error()) + return 0, "", fmt.Errorf("fail to parse number %s: %s", splitArgs[0].Text, err.Error()) } contractIDHex := contract.PlainStrToHexAddr(splitArgs[1].Text) diff --git a/cmd/brick/exec/deployContract.go b/cmd/brick/exec/deployContract.go index 014f2a73e..52bf9bf71 100644 --- a/cmd/brick/exec/deployContract.go +++ b/cmd/brick/exec/deployContract.go @@ -86,7 +86,8 @@ func (c *deployContract) parse(args string) (string, *big.Int, string, string, s return "", nil, "", "", "", fmt.Errorf("need 4 arguments. usage: %s", c.Usage()) } - amount, success := new(big.Int).SetString(splitArgs[1].Text, 10) + amountStr := context.ParseDecimalAmount(splitArgs[1].Text, 18) + amount, success := new(big.Int).SetString(amountStr, 10) if success == false { return "", nil, "", "", "", fmt.Errorf("fail to parse number %s", splitArgs[1].Text) } diff --git a/cmd/brick/exec/getstateAccount.go b/cmd/brick/exec/getstateAccount.go index bf9a6caf5..81bdc8f98 100644 --- a/cmd/brick/exec/getstateAccount.go +++ b/cmd/brick/exec/getstateAccount.go @@ -2,6 +2,7 @@ package exec import ( "fmt" + "math/big" "github.com/aergoio/aergo/v2/cmd/brick/context" "github.com/aergoio/aergo/v2/contract/vm_dummy" @@ -48,7 +49,11 @@ func (c *getStateAccount) parse(args string) (string, string, error) { expectedResult := "" if len(splitArgs) == 2 { - expectedResult = splitArgs[1].Text + expectedResult = context.ParseDecimalAmount(splitArgs[1].Text, 18) + _, success := new(big.Int).SetString(expectedResult, 10) + if expectedResult == "error" || success == false { + return "", "", fmt.Errorf("fail to parse number: %s", splitArgs[1].Text) + } } else if len(splitArgs) > 2 { return "", "", fmt.Errorf("too many arguments. usage: %s", c.Usage()) } diff --git a/cmd/brick/exec/injectAccount.go b/cmd/brick/exec/injectAccount.go index f5938341f..9c605c2e9 100644 --- a/cmd/brick/exec/injectAccount.go +++ b/cmd/brick/exec/injectAccount.go @@ -49,7 +49,8 @@ func (c *injectAccount) parse(args string) (string, *big.Int, error) { return "", nil, fmt.Errorf("need 2 arguments. usage: %s", c.Usage()) } - amount, success := new(big.Int).SetString(splitArgs[1].Text, 10) + amountStr := context.ParseDecimalAmount(splitArgs[1].Text, 18) + amount, success := new(big.Int).SetString(amountStr, 10) if success == false { return "", nil, fmt.Errorf("fail to parse number %s", splitArgs[1].Text) } diff --git a/cmd/brick/exec/sendCoin.go b/cmd/brick/exec/sendCoin.go index e7c93dc5a..6f49f3b51 100644 --- a/cmd/brick/exec/sendCoin.go +++ b/cmd/brick/exec/sendCoin.go @@ -51,9 +51,10 @@ func (c *sendCoin) parse(args string) (string, string, *big.Int, error) { return "", "", nil, fmt.Errorf("need 3 arguments. usage: %s", c.Usage()) } - amount, success := new(big.Int).SetString(splitArgs[2].Text, 10) + amountStr := context.ParseDecimalAmount(splitArgs[2].Text, 18) + amount, success := new(big.Int).SetString(amountStr, 10) if success == false { - return "", "", nil, fmt.Errorf("fail to parse number %s", splitArgs[1].Text) + return "", "", nil, fmt.Errorf("fail to parse number %s", splitArgs[2].Text) } return splitArgs[0].Text, diff --git a/config/config.go b/config/config.go index bec2f5896..8c23b2d0b 100644 --- a/config/config.go +++ b/config/config.go @@ -157,6 +157,8 @@ func (ctx *ServerContext) GetDefaultMempoolConfig() *MempoolConfig { FadeoutPeriod: types.DefaultEvictPeriod, VerifierNumber: runtime.NumCPU(), DumpFilePath: ctx.ExpandPathEnv("$HOME/mempool.dump"), + BlockDeploy: false, + Blacklist: nil, } } diff --git a/config/types.go b/config/types.go index 39705da54..f5e6944e6 100644 --- a/config/types.go +++ b/config/types.go @@ -127,6 +127,8 @@ type MempoolConfig struct { FadeoutPeriod int `mapstructure:"fadeoutperiod" description:"time period for evict transactions(in hour)"` VerifierNumber int `mapstructure:"verifiers" description:"number of concurrent verifier"` DumpFilePath string `mapstructure:"dumpfilepath" description:"file path for recording mempool at process termintation"` + BlockDeploy bool `mapstructure:"blockdeploy" description:"block the deployment of new contracts"` + Blacklist []string `mapstructure:"blacklist" description:"List of account addresses or ids to be blocked"` } // ConsensusConfig defines configurations for consensus service @@ -258,6 +260,10 @@ enablefadeout = {{.Mempool.EnableFadeout}} fadeoutperiod = {{.Mempool.FadeoutPeriod}} verifiers = {{.Mempool.VerifierNumber}} dumpfilepath = "{{.Mempool.DumpFilePath}}" +blockdeploy = {{.Mempool.BlockDeploy}} +blacklist = [{{range .Mempool.Blacklist}} +"{{.}}", {{end}} +] [consensus] enablebp = {{.Consensus.EnableBp}} diff --git a/contract/bignum_module.c b/contract/bignum_module.c index a9b07e953..b1b5cb42f 100644 --- a/contract/bignum_module.c +++ b/contract/bignum_module.c @@ -67,7 +67,15 @@ const char *lua_set_bignum(lua_State *L, char *s) { if (x == NULL) { return mp_num_memory_error; } - if (vm_is_hardfork(L, 3)) { + if (vm_is_hardfork(L, 4)) { + // remove support for octal format and + // keep support for hex (0x) and binary (0b) formats + if (s && s[0]=='0' && s[1]!=0 && s[1]!='x' && s[1]!='b') { + // convert "0123" -> "123" + while (s && s[0]=='0' && s[1]!=0) s++; + } + } else if (vm_is_hardfork(L, 3)) { + // previous code remove support for octal, hex and binary formats while (s && s[0]=='0' && s[1]!=0) s++; } if (mpz_init_set_str(x->mpptr, s, 0) != 0) { @@ -119,7 +127,15 @@ static mp_num Bget(lua_State *L, int i) { if (x == NULL) { luaL_error(L, mp_num_memory_error); } - if (vm_is_hardfork(L, 3)) { + if (vm_is_hardfork(L, 4)) { + // remove support for octal format and + // keep support for hex (0x) and binary (0b) formats + if (s && s[0]=='0' && s[1]!=0 && s[1]!='x' && s[1]!='b') { + // convert "0123" -> "123" + while (s && s[0]=='0' && s[1]!=0) s++; + } + } else if (vm_is_hardfork(L, 3)) { + // previous code remove support for octal, hex and binary formats while (s && s[0]=='0' && s[1]!=0) s++; } if (mpz_init_set_str(x->mpptr, s, 0) != 0) { @@ -255,6 +271,13 @@ static int Bisneg(lua_State *L) { return 1; } +static int Bispos(lua_State *L) { + mp_num a = Bget(L, 1); + lua_gasuse(L, 10); + lua_pushboolean(L, (mpz_sgn(MPZ(a)) > 0)); + return 1; +} + static int Bnumber(lua_State *L) { lua_gasuse(L, 50); Bget(L, 1); @@ -498,7 +521,7 @@ const char *init_bignum() { return NULL; } -static const luaL_Reg R[] = { +static const luaL_Reg bignum_lib_v2[] = { { "__add", Badd }, /* __add(x,y) */ { "__div", Bdiv }, /* __div(x,y) */ { "__eq", Beq }, /* __eq(x,y) */ @@ -532,19 +555,60 @@ static const luaL_Reg R[] = { { NULL, NULL } }; +static const luaL_Reg bignum_lib_v4[] = { + { "__add", Badd }, /* __add(x,y) */ + { "__div", Bdiv }, /* __div(x,y) */ + { "__eq", Beq }, /* __eq(x,y) */ + { "__gc", Bgc }, + { "__lt", Blt }, /* __lt(x,y) */ + { "__mod", Bmod }, /* __mod(x,y) */ + { "__mul", Bmul }, /* __mul(x,y) */ + { "__pow", Bpow }, /* __pow(x,y) */ + { "__sub", Bsub }, /* __sub(x,y) */ + { "__tostring", Btostring}, /* __tostring(x) */ + { "__unm", Bneg }, /* __unm(x) */ + { "add", Badd }, + { "compare", Bcompare}, + { "div", Bdiv }, + { "divmod", Bdivmod }, + { "isneg", Bisneg }, + { "isnegative", Bisneg }, + { "iszero", Biszero }, + { "ispositive", Bispos }, + { "mod", Bmod }, + { "mul", Bmul }, + { "neg", Bneg }, + { "number", Bnumber }, + { "pow", Bpow }, + { "powmod", Bpowmod }, + { "sqrt", Bsqrt }, + { "sub", Bsub }, + { "tonumber", Btonumber}, + { "tostring", Btostring}, + { "isbignum", Bis }, + { "tobyte", Btobyte }, + { "frombyte", Bfrombyte }, + { NULL, NULL } +}; + LUALIB_API int luaopen_bignum(lua_State *L) { - luaL_newmetatable(L,MYTYPE); - lua_setglobal(L,MYNAME); - luaL_register(L,MYNAME,R); + luaL_newmetatable(L, MYTYPE); + lua_setglobal(L, MYNAME); + + if (vm_is_hardfork(L, 4)) { + luaL_register(L, MYNAME, bignum_lib_v4); + } else { + luaL_register(L, MYNAME, bignum_lib_v2); + } - lua_pushliteral(L,"version"); /* version */ - lua_pushliteral(L,MYVERSION); - lua_settable(L,-3); + lua_pushliteral(L, "version"); /* version */ + lua_pushliteral(L, MYVERSION); + lua_settable(L, -3); - lua_pushliteral(L,"__index"); - lua_pushvalue(L,-2); - lua_settable(L,-3); + lua_pushliteral(L, "__index"); + lua_pushvalue(L, -2); + lua_settable(L, -3); lua_pop(L, 1); return 1; diff --git a/contract/contract.go b/contract/contract.go index 16f845270..0002018f4 100644 --- a/contract/contract.go +++ b/contract/contract.go @@ -161,6 +161,17 @@ func checkExecution(txType types.TxType, amount *big.Int, payloadSize int, versi return true, nil } + // transactions with type NORMAL should not call smart contracts + // transactions with type TRANSFER can only call smart contracts when: + // * the amount is greater than 0 + // * the payload is empty (only transfer to "default" function) + if version >= 4 && isContract { + if txType == types.TxType_NORMAL || (txType == types.TxType_TRANSFER && (payloadSize > 0 || types.IsZeroAmount(amount))) { + // emit an error + return false, newVmError(types.ErrTxNotAllowedRecipient) + } + } + // check if the receiver is a not contract if !isDeploy && !isContract { // before the hardfork version 3, all transactions in which the recipient diff --git a/contract/contract_module.c b/contract/contract_module.c index da9b259be..c022a0a9c 100644 --- a/contract/contract_module.c +++ b/contract/contract_module.c @@ -27,10 +27,12 @@ static void reset_amount_info (lua_State *L) { } static int set_value(lua_State *L, const char *str) { + set_call_obj(L, str); if (lua_isnil(L, 1)) { return 1; } + switch(lua_type(L, 1)) { case LUA_TNUMBER: { const char *str = lua_tostring(L, 1); @@ -52,6 +54,7 @@ static int set_value(lua_State *L, const char *str) { default: luaL_error(L, "invalid input"); } + lua_setfield(L, -2, amount_str); return 1; @@ -283,6 +286,7 @@ static int moduleBalance(lua_State *L) { static int modulePcall(lua_State *L) { int argc = lua_gettop(L) - 1; int service = getLuaExecContext(L); + int num_events = luaGetEventCount(L, service); struct luaSetRecoveryPoint_return start_seq; int ret; @@ -295,11 +299,18 @@ static int modulePcall(lua_State *L) { } if ((ret = lua_pcall(L, argc, LUA_MULTRET, 0)) != 0) { + // if out of memory, throw error if (ret == LUA_ERRMEM) { luaL_throwerror(L); } + // add 'success = false' as the first returned value lua_pushboolean(L, false); lua_insert(L, 1); + // drop the events + if (vm_is_hardfork(L, 4)) { + luaDropEvent(L, service, num_events); + } + // revert the contract state if (start_seq.r0 > 0) { char *errStr = luaClearRecovery(L, service, start_seq.r0, true); if (errStr != NULL) { @@ -309,15 +320,24 @@ static int modulePcall(lua_State *L) { } return 2; } + + // add 'success = true' as the first returned value lua_pushboolean(L, true); lua_insert(L, 1); + + // release the recovery point if (start_seq.r0 == 1) { char *errStr = luaClearRecovery(L, service, start_seq.r0, false); if (errStr != NULL) { + if (vm_is_hardfork(L, 4)) { + luaDropEvent(L, service, num_events); + } strPushAndRelease(L, errStr); luaL_throwerror(L); } } + + // return the number of items in the stack return lua_gettop(L); } @@ -335,6 +355,7 @@ static int moduleDeploy(lua_State *L) { lua_gasuse(L, 5000); + // get the amount to transfer to the new contract lua_getfield(L, 1, amount_str); if (lua_isnil(L, -1)) { amount = NULL; @@ -342,7 +363,9 @@ static int moduleDeploy(lua_State *L) { amount = (char *) luaL_checkstring(L, -1); } lua_pop(L, 1); + // get the contract source code or the address to an existing contract contract = (char *) luaL_checkstring(L, 2); + // get the deploy arguments to the constructor function json_args = lua_util_get_json_from_stack(L, 3, lua_gettop(L), false); if (json_args == NULL) { reset_amount_info(L); @@ -356,13 +379,13 @@ static int moduleDeploy(lua_State *L) { strPushAndRelease(L, ret.r1); luaL_throwerror(L); } + free(json_args); reset_amount_info(L); strPushAndRelease(L, ret.r1); if (ret.r0 > 1) { lua_insert(L, -ret.r0); } - return ret.r0; } diff --git a/contract/contract_test.go b/contract/contract_test.go index eaa254eea..1696420cd 100644 --- a/contract/contract_test.go +++ b/contract/contract_test.go @@ -32,30 +32,57 @@ func TestCheckExecution(t *testing.T) { expectExec bool }{ // deploy - {version: 2, txType: types.TxType_NORMAL, amount: types.NewAmount(1, types.Aergo), payloadSize: 1000, isDeploy: true, isContract: false, expectErr: nil, expectExec: true}, - {version: 2, txType: types.TxType_DEPLOY, amount: types.NewAmount(1, types.Aergo), payloadSize: 1000, isDeploy: true, isContract: false, expectErr: nil, expectExec: true}, - {version: 2, txType: types.TxType_REDEPLOY, amount: types.NewAmount(1, types.Aergo), payloadSize: 1000, isDeploy: true, isContract: false, expectErr: nil, expectExec: true}, - {version: 3, txType: types.TxType_NORMAL, amount: types.NewAmount(1, types.Aergo), payloadSize: 1000, isDeploy: true, isContract: false, expectErr: nil, expectExec: true}, - {version: 3, txType: types.TxType_DEPLOY, amount: types.NewAmount(1, types.Aergo), payloadSize: 1000, isDeploy: true, isContract: false, expectErr: nil, expectExec: true}, - {version: 3, txType: types.TxType_REDEPLOY, amount: types.NewAmount(1, types.Aergo), payloadSize: 1000, isDeploy: true, isContract: false, expectErr: nil, expectExec: true}, + {version: 2, txType: types.TxType_NORMAL, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: true, isContract: false, expectErr: nil, expectExec: true}, + {version: 2, txType: types.TxType_DEPLOY, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: true, isContract: false, expectErr: nil, expectExec: true}, + {version: 2, txType: types.TxType_REDEPLOY, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: true, isContract: false, expectErr: nil, expectExec: true}, + {version: 3, txType: types.TxType_NORMAL, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: true, isContract: false, expectErr: nil, expectExec: true}, + {version: 3, txType: types.TxType_DEPLOY, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: true, isContract: false, expectErr: nil, expectExec: true}, + {version: 3, txType: types.TxType_REDEPLOY, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: true, isContract: false, expectErr: nil, expectExec: true}, + {version: 4, txType: types.TxType_NORMAL, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: true, isContract: false, expectErr: nil, expectExec: true}, + {version: 4, txType: types.TxType_DEPLOY, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: true, isContract: false, expectErr: nil, expectExec: true}, + {version: 4, txType: types.TxType_REDEPLOY, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: true, isContract: false, expectErr: nil, expectExec: true}, // recipient is contract - {version: 2, txType: types.TxType_NORMAL, amount: types.NewAmount(1, types.Aergo), payloadSize: 1000, isDeploy: false, isContract: true, expectErr: nil, expectExec: true}, - {version: 2, txType: types.TxType_TRANSFER, amount: types.NewAmount(1, types.Aergo), payloadSize: 1000, isDeploy: false, isContract: true, expectErr: nil, expectExec: true}, - {version: 2, txType: types.TxType_CALL, amount: types.NewAmount(1, types.Aergo), payloadSize: 1000, isDeploy: false, isContract: true, expectErr: nil, expectExec: true}, - {version: 2, txType: types.TxType_FEEDELEGATION, amount: types.NewAmount(1, types.Aergo), payloadSize: 1000, isDeploy: false, isContract: true, expectErr: nil, expectExec: true}, - {version: 3, txType: types.TxType_NORMAL, amount: types.NewAmount(1, types.Aergo), payloadSize: 1000, isDeploy: false, isContract: true, expectErr: nil, expectExec: true}, - {version: 3, txType: types.TxType_TRANSFER, amount: types.NewAmount(1, types.Aergo), payloadSize: 1000, isDeploy: false, isContract: true, expectErr: nil, expectExec: true}, - {version: 3, txType: types.TxType_CALL, amount: types.NewAmount(1, types.Aergo), payloadSize: 1000, isDeploy: false, isContract: true, expectErr: nil, expectExec: true}, - {version: 3, txType: types.TxType_FEEDELEGATION, amount: types.NewAmount(1, types.Aergo), payloadSize: 1000, isDeploy: false, isContract: true, expectErr: nil, expectExec: true}, + {version: 2, txType: types.TxType_NORMAL, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: true, expectErr: nil, expectExec: true}, + {version: 2, txType: types.TxType_TRANSFER, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: true, expectErr: nil, expectExec: true}, + {version: 2, txType: types.TxType_CALL, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: true, expectErr: nil, expectExec: true}, + {version: 2, txType: types.TxType_FEEDELEGATION, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: true, expectErr: nil, expectExec: true}, + {version: 3, txType: types.TxType_NORMAL, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: true, expectErr: nil, expectExec: true}, + {version: 3, txType: types.TxType_TRANSFER, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: true, expectErr: nil, expectExec: true}, + {version: 3, txType: types.TxType_CALL, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: true, expectErr: nil, expectExec: true}, + {version: 3, txType: types.TxType_FEEDELEGATION, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: true, expectErr: nil, expectExec: true}, + {version: 4, txType: types.TxType_NORMAL, amount: types.NewAmount(0,types.Aergo), payloadSize: 0, isDeploy: false, isContract: true, expectErr: newVmError(types.ErrTxNotAllowedRecipient), expectExec: false}, + {version: 4, txType: types.TxType_NORMAL, amount: types.NewAmount(1,types.Aergo), payloadSize: 0, isDeploy: false, isContract: true, expectErr: newVmError(types.ErrTxNotAllowedRecipient), expectExec: false}, + {version: 4, txType: types.TxType_NORMAL, amount: types.NewAmount(0,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: true, expectErr: newVmError(types.ErrTxNotAllowedRecipient), expectExec: false}, + {version: 4, txType: types.TxType_NORMAL, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: true, expectErr: newVmError(types.ErrTxNotAllowedRecipient), expectExec: false}, + {version: 4, txType: types.TxType_TRANSFER, amount: types.NewAmount(1,types.Aergo), payloadSize: 0, isDeploy: false, isContract: true, expectErr: nil, expectExec: true}, + {version: 4, txType: types.TxType_TRANSFER, amount: types.NewAmount(0,types.Aergo), payloadSize: 0, isDeploy: false, isContract: true, expectErr: newVmError(types.ErrTxNotAllowedRecipient), expectExec: false}, + {version: 4, txType: types.TxType_TRANSFER, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: true, expectErr: newVmError(types.ErrTxNotAllowedRecipient), expectExec: false}, + {version: 4, txType: types.TxType_TRANSFER, amount: types.NewAmount(0,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: true, expectErr: newVmError(types.ErrTxNotAllowedRecipient), expectExec: false}, + {version: 4, txType: types.TxType_CALL, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: true, expectErr: nil, expectExec: true}, + {version: 4, txType: types.TxType_CALL, amount: types.NewAmount(1,types.Aergo), payloadSize: 0, isDeploy: false, isContract: true, expectErr: nil, expectExec: true}, + {version: 4, txType: types.TxType_FEEDELEGATION, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: true, expectErr: nil, expectExec: true}, + {version: 4, txType: types.TxType_FEEDELEGATION, amount: types.NewAmount(1,types.Aergo), payloadSize: 0, isDeploy: false, isContract: true, expectErr: nil, expectExec: true}, // recipient is not a contract - {version: 2, txType: types.TxType_NORMAL, amount: types.NewAmount(1, types.Aergo), payloadSize: 1000, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, - {version: 2, txType: types.TxType_TRANSFER, amount: types.NewAmount(1, types.Aergo), payloadSize: 1000, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, - {version: 2, txType: types.TxType_CALL, amount: types.NewAmount(1, types.Aergo), payloadSize: 1000, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, - {version: 2, txType: types.TxType_FEEDELEGATION, amount: types.NewAmount(1, types.Aergo), payloadSize: 1000, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, - {version: 3, txType: types.TxType_NORMAL, amount: types.NewAmount(1, types.Aergo), payloadSize: 1000, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, - {version: 3, txType: types.TxType_TRANSFER, amount: types.NewAmount(1, types.Aergo), payloadSize: 1000, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, - {version: 3, txType: types.TxType_CALL, amount: types.NewAmount(1, types.Aergo), payloadSize: 1000, isDeploy: false, isContract: false, expectErr: nil, expectExec: true}, - {version: 3, txType: types.TxType_FEEDELEGATION, amount: types.NewAmount(1, types.Aergo), payloadSize: 1000, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, + {version: 2, txType: types.TxType_NORMAL, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, + {version: 2, txType: types.TxType_TRANSFER, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, + {version: 2, txType: types.TxType_CALL, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, + {version: 2, txType: types.TxType_FEEDELEGATION, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, + {version: 3, txType: types.TxType_NORMAL, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, + {version: 3, txType: types.TxType_TRANSFER, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, + {version: 3, txType: types.TxType_CALL, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: false, expectErr: nil, expectExec: true}, + {version: 3, txType: types.TxType_FEEDELEGATION, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, + {version: 4, txType: types.TxType_NORMAL, amount: types.NewAmount(0,types.Aergo), payloadSize: 0, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, + {version: 4, txType: types.TxType_NORMAL, amount: types.NewAmount(1,types.Aergo), payloadSize: 0, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, + {version: 4, txType: types.TxType_NORMAL, amount: types.NewAmount(0,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, + {version: 4, txType: types.TxType_NORMAL, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, + {version: 4, txType: types.TxType_TRANSFER, amount: types.NewAmount(1,types.Aergo), payloadSize: 0, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, + {version: 4, txType: types.TxType_TRANSFER, amount: types.NewAmount(0,types.Aergo), payloadSize: 0, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, + {version: 4, txType: types.TxType_TRANSFER, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, + {version: 4, txType: types.TxType_TRANSFER, amount: types.NewAmount(0,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, + {version: 4, txType: types.TxType_CALL, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: false, expectErr: nil, expectExec: true}, + {version: 4, txType: types.TxType_CALL, amount: types.NewAmount(1,types.Aergo), payloadSize: 0, isDeploy: false, isContract: false, expectErr: nil, expectExec: true}, + {version: 4, txType: types.TxType_FEEDELEGATION, amount: types.NewAmount(1,types.Aergo), payloadSize: 1000, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, + {version: 4, txType: types.TxType_FEEDELEGATION, amount: types.NewAmount(1,types.Aergo), payloadSize: 0, isDeploy: false, isContract: false, expectErr: nil, expectExec: false}, } { do_execute, err := checkExecution(test.txType, test.amount, test.payloadSize, test.version, test.isDeploy, test.isContract) assert.Equal(t, test.expectErr, err, "checkExecution(version:%d, txType:%d, amount:%s, payloadSize:%d)", test.version, test.txType, test.amount, test.payloadSize) diff --git a/contract/measure/aef.lua b/contract/measure/aef.lua index 7931e25e3..c27022c79 100644 --- a/contract/measure/aef.lua +++ b/contract/measure/aef.lua @@ -152,6 +152,25 @@ function bignum_fns() local b_b = bignum.number(b) local pi_b = bignum.number(pi) local pi1_b = bignum.number(pi1) + --if system.version() >= 4 then + if type(bignum.ispositive) == "function" then + print("bignum.ispositive") + m1k(bignum.ispositive, nines_b) + m1k(bignum.ispositive, nines1_b) + m1k(bignum.ispositive, nines2_b) + m1k(bignum.ispositive, one_b) + m1k(bignum.ispositive, b_b) + m1k(bignum.ispositive, pi_b) + m1k(bignum.ispositive, pi1_b) + print("bignum.isnegative") + m1k(bignum.isnegative, nines_b) + m1k(bignum.isnegative, nines1_b) + m1k(bignum.isnegative, nines2_b) + m1k(bignum.isnegative, one_b) + m1k(bignum.isnegative, b_b) + m1k(bignum.isnegative, pi_b) + m1k(bignum.isnegative, pi1_b) + end print("bignum.isneg") m1k(bignum.isneg, nines_b) m1k(bignum.isneg, nines1_b) diff --git a/contract/name_module.c b/contract/name_module.c new file mode 100644 index 000000000..457b77d87 --- /dev/null +++ b/contract/name_module.c @@ -0,0 +1,42 @@ +#include +#include +#include "vm.h" +#include "util.h" +#include "_cgo_export.h" + +extern int getLuaExecContext(lua_State *L); + +static int resolve(lua_State *L) { + char *name, *ret; + int service = getLuaExecContext(L); + + lua_gasuse(L, 100); + + name = (char *)luaL_checkstring(L, 1); + ret = luaNameResolve(L, service, name); + + if (ret == NULL) { + lua_pushnil(L); + } else { + // if the returned string starts with `[`, it's an error + if (ret[0] == '[') { + strPushAndRelease(L, ret); + luaL_throwerror(L); + } else { + strPushAndRelease(L, ret); + } + } + + return 1; +} + +static const luaL_Reg name_service_lib[] = { + {"resolve", resolve}, + {NULL, NULL} +}; + +int luaopen_name(lua_State *L) { + luaL_register(L, "name_service", name_service_lib); + lua_pop(L, 1); + return 1; +} diff --git a/contract/name_module.h b/contract/name_module.h new file mode 100644 index 000000000..9099f9ceb --- /dev/null +++ b/contract/name_module.h @@ -0,0 +1,8 @@ +#ifndef _NAME_MODULE_H +#define _NAME_MODULE_H + +#include "lua.h" + +extern int luaopen_name(lua_State *L); + +#endif /* _NAME_MODULE_H */ \ No newline at end of file diff --git a/contract/system_module.c b/contract/system_module.c index dc5d82613..45f2b480f 100644 --- a/contract/system_module.c +++ b/contract/system_module.c @@ -15,7 +15,7 @@ static int systemPrint(lua_State *L) { int service = getLuaExecContext(L); lua_gasuse(L, 100); - jsonValue = lua_util_get_json_from_stack (L, 1, lua_gettop(L), true); + jsonValue = lua_util_get_json_from_stack(L, 1, lua_gettop(L), true); if (jsonValue == NULL) { luaL_throwerror(L); } @@ -50,7 +50,7 @@ int setItemWithPrefix(lua_State *L) { dbKey = getDbKey(L, &keylen); - jsonValue = lua_util_get_json (L, 2, false); + jsonValue = lua_util_get_json(L, 2, false); if (jsonValue == NULL) { luaL_throwerror(L); } @@ -423,6 +423,56 @@ static int lua_random(lua_State *L) { return 1; } +static int toPubkey(lua_State *L) { + char *address, *ret; + + lua_gasuse(L, 100); + + // get the function argument + address = (char *)luaL_checkstring(L, 1); + // convert the address to public key + ret = luaToPubkey(L, address); + + if (ret == NULL) { + lua_pushnil(L); + } else { + // if the returned string starts with `[`, it's an error + if (ret[0] == '[') { + strPushAndRelease(L, ret); + luaL_throwerror(L); + } else { + strPushAndRelease(L, ret); + } + } + + return 1; +} + +static int toAddress(lua_State *L) { + char *pubkey, *ret; + + lua_gasuse(L, 100); + + // get the function argument + pubkey = (char *)luaL_checkstring(L, 1); + // convert the public key to an address + ret = luaToAddress(L, pubkey); + + if (ret == NULL) { + lua_pushnil(L); + } else { + // if the returned string starts with `[`, it's an error + if (ret[0] == '[') { + strPushAndRelease(L, ret); + luaL_throwerror(L); + } else { + strPushAndRelease(L, ret); + } + } + + return 1; +} + static int is_contract(lua_State *L) { char *contract; int service = getLuaExecContext(L); @@ -461,7 +511,13 @@ static int is_fee_delegation(lua_State *L) { return 1; } -static const luaL_Reg sys_lib[] = { +static int system_version(lua_State *L) { + lua_gasuse(L, 100); + lua_pushinteger(L, luaL_hardforkversion(L)); + return 1; +} + +static const luaL_Reg system_lib_v1[] = { {"print", systemPrint}, {"setItem", setItem}, {"getItem", getItem}, @@ -483,8 +539,37 @@ static const luaL_Reg sys_lib[] = { {NULL, NULL} }; +static const luaL_Reg system_lib_v4[] = { + {"print", systemPrint}, + {"setItem", setItem}, + {"getItem", getItem}, + {"getSender", getSender}, + {"getCreator", getCreator}, + {"getTxhash", getTxhash}, + {"getBlockheight", getBlockHeight}, + {"getTimestamp", getTimestamp}, + {"getContractID", getContractID}, + {"getOrigin", getOrigin}, + {"getAmount", getAmount}, + {"getPrevBlockHash", getPrevBlockHash}, + {"date", os_date}, + {"time", os_time}, + {"difftime", os_difftime}, + {"random", lua_random}, + {"toPubKey", toPubkey}, + {"toAddress", toAddress}, + {"isContract", is_contract}, + {"isFeeDelegation", is_fee_delegation}, + {"version", system_version}, + {NULL, NULL} +}; + int luaopen_system(lua_State *L) { - luaL_register(L, "system", sys_lib); + if (vm_is_hardfork(L, 4)) { + luaL_register(L, "system", system_lib_v4); + } else { + luaL_register(L, "system", system_lib_v1); + } lua_pop(L, 1); return 1; } diff --git a/contract/vm.c b/contract/vm.c index 1f3ea6c69..bbdc1aa9b 100644 --- a/contract/vm.c +++ b/contract/vm.c @@ -4,6 +4,7 @@ #include "vm.h" #include "system_module.h" #include "contract_module.h" +#include "name_module.h" #include "db_module.h" #include "state_module.h" #include "crypto_module.h" @@ -23,6 +24,13 @@ extern void (*lj_internal_view_end)(lua_State *); void vm_internal_view_start(lua_State *L); void vm_internal_view_end(lua_State *L); +int getLuaExecContext(lua_State *L) { + int service = luaL_service(L); + if (service < 0) + luaL_error(L, "not permitted state referencing at global scope"); + return service; +} + #ifdef MEASURE static int nsec(lua_State *L) { lua_pushnumber(L, luaL_nanosecond(L)); @@ -41,10 +49,39 @@ static void preloadModules(lua_State *L) { luaopen_bignum(L); luaopen_utf8(L); + if (vm_is_hardfork(L, 4)) { + luaopen_name(L); + } + if (!isPublic()) { luaopen_db(L); } + if (vm_is_hardfork(L, 4)) { + lua_getglobal(L, "_G"); + // disable getmetatable + lua_pushnil(L); + lua_setfield(L, -2, "getmetatable"); + // disable setmetatable + lua_pushnil(L); + lua_setfield(L, -2, "setmetatable"); + // disable rawget + lua_pushnil(L); + lua_setfield(L, -2, "rawget"); + // disable rawset + lua_pushnil(L); + lua_setfield(L, -2, "rawset"); + // disable rawequal + lua_pushnil(L); + lua_setfield(L, -2, "rawequal"); + lua_pop(L, 1); + // disable string.dump + lua_getglobal(L, "string"); + lua_pushnil(L); + lua_setfield(L, -2, "dump"); + lua_pop(L, 1); + } + #ifdef MEASURE lua_register(L, "nsec", nsec); luaopen_jit(L); @@ -61,18 +98,181 @@ static void preloadModules(lua_State *L) { #endif } +// overridden version of pcall +// used to rollback state and drop events upon error +static int pcall(lua_State *L) { + int argc = lua_gettop(L); + int service = getLuaExecContext(L); + int num_events = luaGetEventCount(L, service); + struct luaSetRecoveryPoint_return start_seq; + int ret; + + if (argc < 1) { + return luaL_error(L, "pcall: not enough arguments"); + } + + lua_gasuse(L, 300); + + start_seq = luaSetRecoveryPoint(L, service); + if (start_seq.r0 < 0) { + strPushAndRelease(L, start_seq.r1); + luaL_throwerror(L); + } + + // the stack is like this: + // func arg1 arg2 ... argn + + // call the function + ret = lua_pcall(L, argc - 1, LUA_MULTRET, 0); + + // if failed, drop the events + if (ret != 0) { + if (vm_is_hardfork(L, 4)) { + luaDropEvent(L, service, num_events); + } + } + + // throw the error if out of memory + if (ret == LUA_ERRMEM) { + luaL_throwerror(L); + } + + // insert the status at the bottom of the stack + lua_pushboolean(L, ret == 0); + lua_insert(L, 1); + + // release the recovery point or revert the contract state + if (start_seq.r0 > 0) { + bool is_error = (ret != 0); + char *errStr = luaClearRecovery(L, service, start_seq.r0, is_error); + if (errStr != NULL) { + if (vm_is_hardfork(L, 4)) { + luaDropEvent(L, service, num_events); + } + strPushAndRelease(L, errStr); + luaL_throwerror(L); + } + } + + // return the number of items in the stack + return lua_gettop(L); +} + +// overridden version of xpcall +// used to rollback state and drop events upon error +static int xpcall(lua_State *L) { + int argc = lua_gettop(L); + int service = getLuaExecContext(L); + int num_events = luaGetEventCount(L, service); + struct luaSetRecoveryPoint_return start_seq; + int ret, errfunc; + + if (argc < 2) { + return luaL_error(L, "xpcall: not enough arguments"); + } + + lua_gasuse(L, 300); + + start_seq = luaSetRecoveryPoint(L, service); + if (start_seq.r0 < 0) { + strPushAndRelease(L, start_seq.r1); + luaL_throwerror(L); + } + + // the stack is like this: + // func errfunc arg1 arg2 ... argn + + // check the error handler + errfunc = 2; + if (!lua_isfunction(L, errfunc)) { + return luaL_error(L, "xpcall: error handler is not a function"); + } + + // move the error handler to the first position + lua_pushvalue(L, 1); // function + lua_pushvalue(L, 2); // error handler + lua_replace(L, 1); // 1: error handler + lua_replace(L, 2); // 2: function + + // now the stack is like this: + // errfunc func arg1 arg2 ... argn + + // update the error handler position + errfunc = 1; + + // call the function + ret = lua_pcall(L, argc - 2, LUA_MULTRET, errfunc); + + // if failed, drop the events + if (ret != 0) { + if (vm_is_hardfork(L, 4)) { + luaDropEvent(L, service, num_events); + } + } + + // throw the error if out of memory + if (ret == LUA_ERRMEM) { + luaL_throwerror(L); + } + + // ensure the stack has 1 free slot + if (!lua_checkstack(L, 1)) { + // return: false, "stack overflow" + lua_settop(L, 0); + lua_pushboolean(L, 0); + lua_pushliteral(L, "stack overflow"); + return 2; + } + + // store the status at the bottom of the stack, replacing the error handler + lua_pushboolean(L, ret == 0); + lua_replace(L, 1); + + // release the recovery point or revert the contract state + if (start_seq.r0 > 0) { + bool is_error = (ret != 0); + char *errStr = luaClearRecovery(L, service, start_seq.r0, is_error); + if (errStr != NULL) { + if (vm_is_hardfork(L, 4)) { + luaDropEvent(L, service, num_events); + } + strPushAndRelease(L, errStr); + luaL_throwerror(L); + } + } + + // return the number of items in the stack + return lua_gettop(L); +} + +static const struct luaL_Reg _basefuncs[] = { + {"pcall", pcall}, + {"xpcall", xpcall}, + {NULL, NULL}}; + +static void override_basefuncs(lua_State *L) { + // override Lua builtin functions + lua_getglobal(L, "_G"); + luaL_register(L, NULL, _basefuncs); + lua_pop(L, 1); +} + static int loadLibs(lua_State *L) { luaL_openlibs(L); preloadModules(L); + if (vm_is_hardfork(L, 4)) { + // override pcall to drop events upon error + override_basefuncs(L); + } return 0; } lua_State *vm_newstate(int hardfork_version) { - int status; lua_State *L = luaL_newstate(hardfork_version); if (L == NULL) return NULL; - status = lua_cpcall(L, loadLibs, NULL); + // hardfork version set before loading modules + int status = lua_cpcall(L, loadLibs, NULL); if (status != 0) return NULL; return L; @@ -91,14 +291,6 @@ void initViewFunction() { lj_internal_view_end = vm_internal_view_end; } -int getLuaExecContext(lua_State *L) { - int service = luaL_service(L); - if (service < 0) { - luaL_error(L, "not permitted state referencing at global scope"); - } - return service; -} - bool vm_is_hardfork(lua_State *L, int version) { int v = luaL_hardforkversion(L); return v >= version; diff --git a/contract/vm.go b/contract/vm.go index 99ecb721e..f0372d440 100644 --- a/contract/vm.go +++ b/contract/vm.go @@ -44,6 +44,7 @@ import ( "github.com/aergoio/aergo/v2/state/statedb" "github.com/aergoio/aergo/v2/types" "github.com/aergoio/aergo/v2/types/dbkey" + "github.com/aergoio/aergo/v2/blacklist" jsoniter "github.com/json-iterator/go" ) @@ -312,6 +313,16 @@ func newExecutor( } ctx.callDepth++ + if blacklist.Check(types.EncodeAddress(contractId)) { + ce := &executor{ + code: contract, + ctx: ctx, + } + ce.err = fmt.Errorf("contract not available") + ctrLgr.Error().Err(ce.err).Str("contract", types.EncodeAddress(contractId)).Msg("blocked contract") + return ce + } + ce := &executor{ code: contract, L: GetLState(), @@ -818,22 +829,20 @@ func Call( var err error var ci types.CallInfo - var contract []byte + var bytecode []byte // get contract if ctx.isMultiCall { - contract = getMultiCallContract(contractState) + bytecode = getMultiCallContract(contractState) } else { - contract = getContract(contractState, ctx.bs) + bytecode = getContractCode(contractState, ctx.bs) } - if contract != nil { + if bytecode != nil { // get call arguments if ctx.isMultiCall { err = getMultiCallInfo(&ci, payload) - } else { - if len(payload) > 0 { - err = getCallInfo(&ci, payload, contractAddress) - } + } else if len(payload) > 0 { + err = getCallInfo(&ci, payload, contractAddress) } } else { addr := types.EncodeAddress(contractAddress) @@ -850,14 +859,16 @@ func Call( // create a new executor contexts[ctx.service] = ctx - ce := newExecutor(contract, contractAddress, ctx, &ci, ctx.curContract.amount, false, false, contractState) + ce := newExecutor(bytecode, contractAddress, ctx, &ci, ctx.curContract.amount, false, false, contractState) defer ce.close() - startTime := time.Now() - // execute the contract call - ce.call(callMaxInstLimit, nil) - vmExecTime := time.Now().Sub(startTime).Microseconds() - vmLogger.Trace().Int64("execµs", vmExecTime).Stringer("txHash", types.LogBase58(ce.ctx.txHash)).Msg("tx execute time in vm") + if ce.err == nil { + startTime := time.Now() + // execute the contract call + ce.call(callMaxInstLimit, nil) + vmExecTime := time.Now().Sub(startTime).Microseconds() + vmLogger.Trace().Int64("execµs", vmExecTime).Stringer("txHash", types.LogBase58(ce.ctx.txHash)).Msg("tx execute time in vm") + } // check if there is an error err = ce.err @@ -927,19 +938,47 @@ func setRandomSeed(ctx *vmContext) { ctx.seed = rand.New(randSrc) } -func setContract(contractState *statedb.ContractState, contractAddress, payload []byte) ([]byte, []byte, error) { +func setContract(contractState *statedb.ContractState, contractAddress, payload []byte, ctx *vmContext) ([]byte, []byte, error) { + // the payload contains: + // on V3: bytecode + ABI + constructor arguments + // on V4: lua code + constructor arguments codePayload := luacUtil.LuaCodePayload(payload) if _, err := codePayload.IsValidFormat(); err != nil { ctrLgr.Warn().Err(err).Str("contract", types.EncodeAddress(contractAddress)).Msg("deploy") return nil, nil, err } - code := codePayload.Code() - err := contractState.SetCode(code.Bytes()) + code := codePayload.Code() // type: LuaCode + + var sourceCode []byte + var bytecodeABI []byte + var err error + + // if hardfork version 4 + if ctx.blockInfo.ForkVersion >= 4 { + // the payload must be lua code. compile it to bytecode + sourceCode = code + bytecodeABI, err = Compile(string(sourceCode), nil) + if err != nil { + ctrLgr.Warn().Err(err).Str("contract", types.EncodeAddress(contractAddress)).Msg("deploy") + return nil, nil, err + } + } else { + // on previous hardfork versions the payload is bytecode + bytecodeABI = code + } + + // save the bytecode to the contract state + err = contractState.SetCode(sourceCode, bytecodeABI) if err != nil { return nil, nil, err } - contract := getContract(contractState, nil) - if contract == nil { + + // extract the bytecode + bytecode := luacUtil.LuaCode(bytecodeABI).ByteCode() + + // check if it was properly stored + savedBytecode := getContractCode(contractState, nil) + if savedBytecode == nil || !bytes.Equal(savedBytecode, bytecode) { err = fmt.Errorf("cannot deploy contract %s", types.EncodeAddress(contractAddress)) ctrLgr.Warn().Str("error", "cannot load contract").Str( "contract", @@ -948,16 +987,16 @@ func setContract(contractState *statedb.ContractState, contractAddress, payload return nil, nil, err } - return contract, codePayload.Args(), nil + return bytecode, codePayload.Args(), nil } func Create( contractState *statedb.ContractState, - code, contractAddress []byte, + payload, contractAddress []byte, ctx *vmContext, ) (string, []*types.Event, *big.Int, error) { - if len(code) == 0 { + if len(payload) == 0 { return "", nil, ctx.usedFee(), errors.New("contract code is required") } @@ -966,7 +1005,7 @@ func Create( } // save the contract code - contract, args, err := setContract(contractState, contractAddress, code) + bytecode, args, err := setContract(contractState, contractAddress, payload, ctx) if err != nil { return "", nil, ctx.usedFee(), err } @@ -997,11 +1036,13 @@ func Create( } // create a new executor for the constructor - ce := newExecutor(contract, contractAddress, ctx, &ci, ctx.curContract.amount, true, false, contractState) + ce := newExecutor(bytecode, contractAddress, ctx, &ci, ctx.curContract.amount, true, false, contractState) defer ce.close() - // call the constructor - ce.call(callMaxInstLimit, nil) + if err == nil { + // call the constructor + ce.call(callMaxInstLimit, nil) + } // check if the call failed err = ce.err @@ -1093,8 +1134,8 @@ func freeContextSlot(ctx *vmContext) { func Query(contractAddress []byte, bs *state.BlockState, cdb ChainAccessor, contractState *statedb.ContractState, queryInfo []byte) (res []byte, err error) { var ci types.CallInfo - contract := getContract(contractState, bs) - if contract != nil { + bytecode := getContractCode(contractState, bs) + if bytecode != nil { err = getCallInfo(&ci, queryInfo, contractAddress) } else { addr := types.EncodeAddress(contractAddress) @@ -1120,7 +1161,7 @@ func Query(contractAddress []byte, bs *state.BlockState, cdb ChainAccessor, cont ctrLgr.Debug().Str("abi", string(queryInfo)).Str("contract", types.EncodeAddress(contractAddress)).Msg("query") } - ce := newExecutor(contract, contractAddress, ctx, &ci, ctx.curContract.amount, false, false, contractState) + ce := newExecutor(bytecode, contractAddress, ctx, &ci, ctx.curContract.amount, false, false, contractState) defer ce.close() defer func() { if dbErr := ce.closeQuerySql(); dbErr != nil { @@ -1128,7 +1169,9 @@ func Query(contractAddress []byte, bs *state.BlockState, cdb ChainAccessor, cont } }() - ce.call(queryMaxInstLimit, nil) + if err == nil { + ce.call(queryMaxInstLimit, nil) + } return []byte(ce.jsonRet), ce.err } @@ -1161,8 +1204,8 @@ func CheckFeeDelegation(contractAddress []byte, bs *state.BlockState, bi *types. return fmt.Errorf("%s function is not declared of fee delegation", ci.Name) } - contract := getContract(contractState, bs) - if contract == nil { + bytecode := getContractCode(contractState, bs) + if bytecode == nil { addr := types.EncodeAddress(contractAddress) ctrLgr.Warn().Str("error", "not found contract").Str("contract", addr).Msg("checkFeeDelegation") err = fmt.Errorf("not found contract %s", addr) @@ -1194,7 +1237,7 @@ func CheckFeeDelegation(contractAddress []byte, bs *state.BlockState, bi *types. ci.Args = append([]interface{}{ci.Name}, ci.Args...) ci.Name = checkFeeDelegationFn - ce := newExecutor(contract, contractAddress, ctx, &ci, ctx.curContract.amount, false, true, contractState) + ce := newExecutor(bytecode, contractAddress, ctx, &ci, ctx.curContract.amount, false, true, contractState) defer ce.close() defer func() { if dbErr := ce.rollbackToSavepoint(); dbErr != nil { @@ -1202,7 +1245,9 @@ func CheckFeeDelegation(contractAddress []byte, bs *state.BlockState, bi *types. } }() - ce.call(queryMaxInstLimit, nil) + if err == nil { + ce.call(queryMaxInstLimit, nil) + } if ce.err != nil { return ce.err @@ -1230,7 +1275,7 @@ func getCode(contractState *statedb.ContractState, bs *state.BlockState) ([]byte return code, nil } -func getContract(contractState *statedb.ContractState, bs *state.BlockState) []byte { +func getContractCode(contractState *statedb.ContractState, bs *state.BlockState) []byte { // the code from multicall is not loaded, because there is no code hash if len(contractState.GetCodeHash()) == 0 { return nil diff --git a/contract/vm_callback.go b/contract/vm_callback.go index b46e38e3b..9dc66009c 100644 --- a/contract/vm_callback.go +++ b/contract/vm_callback.go @@ -54,7 +54,8 @@ var ( ) const ( - maxEventCnt = 50 + maxEventCntV2 = 50 + maxEventCntV4 = 128 maxEventNameSize = 64 maxEventArgSize = 4096 luaCallCountDeduc = 1000 @@ -66,6 +67,14 @@ func init() { zeroBig = types.NewZeroAmount() } +func maxEventCnt(ctx *vmContext) int32 { + if ctx.blockInfo.ForkVersion >= 4 { + return maxEventCntV4 + } else { + return maxEventCntV2 + } +} + //export luaSetDB func luaSetDB(L *LState, service C.int, key unsafe.Pointer, keyLen C.int, value *C.char) *C.char { ctx := contexts[service] @@ -217,7 +226,7 @@ func luaCallContract(L *LState, service C.int, contractId *C.char, fname *C.char aid := types.ToAccountID(cid) // read the amount for the contract call - amountBig, err := transformAmount(C.GoString(amount)) + amountBig, err := transformAmount(C.GoString(amount), ctx.blockInfo.ForkVersion) if err != nil { return -1, C.CString("[Contract.LuaCallContract] invalid amount: " + err.Error()) } @@ -229,8 +238,8 @@ func luaCallContract(L *LState, service C.int, contractId *C.char, fname *C.char } // check if the contract exists - callee := getContract(cs.ctrState, ctx.bs) - if callee == nil { + bytecode := getContractCode(cs.ctrState, ctx.bs) + if bytecode == nil { return -1, C.CString("[Contract.LuaCallContract] cannot find contract " + C.GoString(contractId)) } @@ -247,7 +256,7 @@ func luaCallContract(L *LState, service C.int, contractId *C.char, fname *C.char // get the remaining gas from the parent LState ctx.refreshRemainingGas(L) // create a new executor with the remaining gas on the child LState - ce := newExecutor(callee, cid, ctx, &ci, amountBig, false, false, cs.ctrState) + ce := newExecutor(bytecode, cid, ctx, &ci, amountBig, false, false, cs.ctrState) defer func() { // close the executor, closes also the child LState ce.close() @@ -349,8 +358,8 @@ func luaDelegateCallContract(L *LState, service C.int, contractId *C.char, } // check if the contract exists - contract := getContract(contractState, ctx.bs) - if contract == nil { + bytecode := getContractCode(contractState, ctx.bs) + if bytecode == nil { return -1, C.CString("[Contract.LuaDelegateCallContract] cannot find contract " + contractIdStr) } @@ -365,7 +374,7 @@ func luaDelegateCallContract(L *LState, service C.int, contractId *C.char, // get the remaining gas from the parent LState ctx.refreshRemainingGas(L) // create a new executor with the remaining gas on the child LState - ce := newExecutor(contract, cid, ctx, &ci, zeroBig, false, false, contractState) + ce := newExecutor(bytecode, cid, ctx, &ci, zeroBig, false, false, contractState) defer func() { // close the executor, closes also the child LState ce.close() @@ -443,7 +452,7 @@ func luaSendAmount(L *LState, service C.int, contractId *C.char, amount *C.char) } // read the amount to be sent - amountBig, err := transformAmount(C.GoString(amount)) + amountBig, err := transformAmount(C.GoString(amount), ctx.blockInfo.ForkVersion) if err != nil { return C.CString("[Contract.LuaSendAmount] invalid amount: " + err.Error()) } @@ -486,15 +495,15 @@ func luaSendAmount(L *LState, service C.int, contractId *C.char, amount *C.char) ci.Name = "default" // get the contract code - code := getContract(cs.ctrState, ctx.bs) - if code == nil { + bytecode := getContractCode(cs.ctrState, ctx.bs) + if bytecode == nil { return C.CString("[Contract.LuaSendAmount] cannot find contract:" + C.GoString(contractId)) } // get the remaining gas from the parent LState ctx.refreshRemainingGas(L) // create a new executor with the remaining gas on the child LState - ce := newExecutor(code, cid, ctx, &ci, amountBig, false, false, cs.ctrState) + ce := newExecutor(bytecode, cid, ctx, &ci, amountBig, false, false, cs.ctrState) defer func() { // close the executor, closes also the child LState ce.close() @@ -940,11 +949,30 @@ 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) { +func transformAmount(amountStr string, forkVersion int32) (*big.Int, error) { if len(amountStr) == 0 { return zeroBig, nil } + if forkVersion >= 4 { + // Check for amount in decimal format + if strings.Contains(amountStr,".") && strings.HasSuffix(strings.ToLower(amountStr),"aergo") { + // Extract the part before the unit + decimalAmount := amountStr[:len(amountStr)-5] + decimalAmount = strings.TrimRight(decimalAmount, " ") + // Parse the decimal amount + decimalAmount = parseDecimalAmount(decimalAmount, 18) + if decimalAmount == "error" { + return nil, errors.New("converting error for BigNum: " + amountStr) + } + amount, valid := new(big.Int).SetString(decimalAmount, 10) + if !valid { + return nil, errors.New("converting error for BigNum: " + amountStr) + } + return amount, nil + } + } + totalAmount := new(big.Int) remainingStr := amountStr @@ -990,18 +1018,53 @@ func transformAmount(amountStr string) (*big.Int, error) { return totalAmount, nil } +// convert decimal amount into big integer string +func parseDecimalAmount(str string, num_decimals int) string { + // Get the integer and decimal parts + idx := strings.Index(str, ".") + if idx == -1 { + return str + } + p1 := str[0:idx] + p2 := str[idx+1:] + + // Check for another decimal point + if strings.Index(p2, ".") != -1 { + return "error" + } + + // Compute the amount of zero digits to add + to_add := num_decimals - len(p2) + if to_add > 0 { + p2 = p2 + strings.Repeat("0", to_add) + } else if to_add < 0 { + // Do not truncate decimal amounts + return "error" + } + + // Join the integer and decimal parts + str = p1 + p2 + + // Remove leading zeros + str = strings.TrimLeft(str, "0") + if str == "" { + str = "0" + } + return str +} + // 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) +func parseAndConvert(subStr, unit string, mulUnit *big.Int, fullStr string) (*big.Int, error) { + subStr = strings.TrimSpace(subStr) - // Convert the trimmed string to a big integer - amountBig, valid := new(big.Int).SetString(trimmedStr, 10) + // Convert the string to a big integer + amountBig, valid := new(big.Int).SetString(subStr, 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)) + return nil, errors.New("converting error for " + dataType + ": " + strings.TrimSpace(fullStr)) } // Check for negative amounts @@ -1039,7 +1102,8 @@ func luaDeployContract( bs := ctx.bs // contract code - var code []byte + var codeABI []byte + var sourceCode []byte // check if contract name or address is given cid, err := getAddressNameResolved(contractStr, bs) @@ -1050,20 +1114,21 @@ func luaDeployContract( return -1, C.CString("[Contract.LuaDeployContract]" + err.Error()) } // read the contract code - code, err = contractState.GetCode() + codeABI, err = contractState.GetCode() if err != nil { return -1, C.CString("[Contract.LuaDeployContract]" + err.Error()) - } else if len(code) == 0 { + } else if len(codeABI) == 0 { return -1, C.CString("[Contract.LuaDeployContract]: not found code") } + sourceCode = contractState.GetSourceCode() } // compile contract code if not found - if len(code) == 0 { + if len(codeABI) == 0 { if ctx.blockInfo.ForkVersion >= 2 { - code, err = Compile(contractStr, L) + codeABI, err = Compile(contractStr, L) } else { - code, err = Compile(contractStr, nil) + codeABI, err = Compile(contractStr, nil) } if err != nil { if C.luaL_hasuncatchablerror(L) != C.int(0) && @@ -1072,12 +1137,12 @@ func luaDeployContract( } else if err == ErrVmStart { return -1, C.CString("[Contract.LuaDeployContract] get luaState error") } - return -1, C.CString("[Contract.LuaDeployContract]compile error:" + err.Error()) } + sourceCode = []byte(contractStr) } - err = ctx.addUpdateSize(int64(len(code))) + err = ctx.addUpdateSize(int64(len(codeABI) + len(sourceCode))) if err != nil { return -1, C.CString("[Contract.LuaDeployContract]:" + err.Error()) } @@ -1098,7 +1163,7 @@ func luaDeployContract( ctx.callState[newContract.AccountID()] = cs // read the amount transferred to the contract - amountBig, err := transformAmount(C.GoString(amount)) + amountBig, err := transformAmount(C.GoString(amount), ctx.blockInfo.ForkVersion) if err != nil { return -1, C.CString("[Contract.LuaDeployContract]value not proper format:" + err.Error()) } @@ -1142,10 +1207,10 @@ func luaDeployContract( ctx.curContract = prevContractInfo }() - runCode := util.LuaCode(code).ByteCode() + bytecode := util.LuaCode(codeABI).ByteCode() // save the contract code - err = contractState.SetCode(code) + err = contractState.SetCode(sourceCode, codeABI) if err != nil { return -1, C.CString("[Contract.LuaDeployContract]:" + err.Error()) } @@ -1159,7 +1224,7 @@ func luaDeployContract( // get the remaining gas from the parent LState ctx.refreshRemainingGas(L) // create a new executor with the remaining gas on the child LState - ce := newExecutor(runCode, newContract.ID(), ctx, &ci, amountBig, true, false, contractState) + ce := newExecutor(bytecode, newContract.ID(), ctx, &ci, amountBig, true, false, contractState) defer func() { // close the executor, which will close the child LState ce.close() @@ -1239,8 +1304,8 @@ func luaEvent(L *LState, service C.int, eventName *C.char, args *C.char) *C.char if ctx.isQuery == true || ctx.nestedView > 0 { return C.CString("[Contract.Event] event not permitted in query") } - if ctx.eventCount >= maxEventCnt { - return C.CString(fmt.Sprintf("[Contract.Event] exceeded the maximum number of events(%d)", maxEventCnt)) + if ctx.eventCount >= maxEventCnt(ctx) { + return C.CString(fmt.Sprintf("[Contract.Event] exceeded the maximum number of events(%d)", maxEventCnt(ctx))) } if len(C.GoString(eventName)) > maxEventNameSize { return C.CString(fmt.Sprintf("[Contract.Event] exceeded the maximum length of event name(%d)", maxEventNameSize)) @@ -1261,6 +1326,61 @@ func luaEvent(L *LState, service C.int, eventName *C.char, args *C.char) *C.char return nil } +//export luaGetEventCount +func luaGetEventCount(L *LState, service C.int) C.int { + eventCount := contexts[service].eventCount + if ctrLgr.IsDebugEnabled() { + ctrLgr.Debug().Int32("eventCount", eventCount).Msg("get event count") + } + return C.int(eventCount) +} + +//export luaDropEvent +func luaDropEvent(L *LState, service C.int, from C.int) { + // Drop all the events after the given index. + ctx := contexts[service] + if ctrLgr.IsDebugEnabled() { + ctrLgr.Debug().Int32("from", int32(from)).Int("len", len(ctx.events)).Msg("drop events") + } + if from >= 0 { + ctx.events = ctx.events[:from] + ctx.eventCount = int32(len(ctx.events)) + } +} + +//export luaToPubkey +func luaToPubkey(L *LState, address *C.char) *C.char { + // check the length of address + if len(C.GoString(address)) != types.EncodedAddressLength { + return C.CString("[Contract.LuaToPubkey] invalid address length") + } + // decode the address in string format to bytes (public key) + pubkey, err := types.DecodeAddress(C.GoString(address)) + if err != nil { + return C.CString("[Contract.LuaToPubkey] invalid address") + } + // return the public key in hex format + return C.CString("0x" + hex.Encode(pubkey)) +} + +//export luaToAddress +func luaToAddress(L *LState, pubkey *C.char) *C.char { + // decode the pubkey in hex format to bytes + pubkeyBytes, err := decodeHex(C.GoString(pubkey)) + if err != nil { + return C.CString("[Contract.LuaToAddress] invalid public key") + } + // check the length of pubkey + if len(pubkeyBytes) != types.AddressLength { + return C.CString("[Contract.LuaToAddress] invalid public key length") + // or convert the pubkey to compact format - SerializeCompressed() + } + // encode the pubkey in bytes to an address in string format + address := types.EncodeAddress(pubkeyBytes) + // return the address + return C.CString(address) +} + //export luaIsContract func luaIsContract(L *LState, service C.int, contractId *C.char) (C.int, *C.char) { @@ -1282,6 +1402,27 @@ func luaIsContract(L *LState, service C.int, contractId *C.char) (C.int, *C.char return C.int(len(cs.accState.CodeHash())), nil } +//export luaNameResolve +func luaNameResolve(L *LState, service C.int, name_or_address *C.char) *C.char { + ctx := contexts[service] + if ctx == nil { + return C.CString("[Contract.LuaNameResolve] contract state not found") + } + var addr []byte + var err error + account := C.GoString(name_or_address) + if len(account) == types.EncodedAddressLength { + // also checks if valid address + addr, err = types.DecodeAddress(account) + } else { + addr, err = name.Resolve(ctx.bs, []byte(account), false) + } + if err != nil { + return C.CString("[Contract.LuaNameResolve] " + err.Error()) + } + return C.CString(types.EncodeAddress(addr)) +} + //export luaGovernance func luaGovernance(L *LState, service C.int, gType C.char, arg *C.char) *C.char { @@ -1300,7 +1441,7 @@ func luaGovernance(L *LState, service C.int, gType C.char, arg *C.char) *C.char switch gType { case 'S', 'U': var err error - amountBig, err = transformAmount(C.GoString(arg)) + amountBig, err = transformAmount(C.GoString(arg), ctx.blockInfo.ForkVersion) if err != nil { return C.CString("[Contract.LuaGovernance] invalid amount: " + err.Error()) } diff --git a/contract/vm_callback_test.go b/contract/vm_callback_test.go index 5f5b9d84f..ceeec753e 100644 --- a/contract/vm_callback_test.go +++ b/contract/vm_callback_test.go @@ -3,12 +3,16 @@ package contract import ( "errors" "math/big" + "strings" "testing" "github.com/aergoio/aergo/v2/types" "github.com/stretchr/testify/assert" ) +const min_version int32 = 2 +const max_version int32 = 4 + func bigIntFromString(str string) *big.Int { bigInt, success := new(big.Int).SetString(str, 10) if !success { @@ -98,13 +102,6 @@ func TestTransformAmount(t *testing.T) { {"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")}, @@ -121,16 +118,128 @@ func TestTransformAmount(t *testing.T) { {"5e+3aergo", nil, errors.New("converting error for BigNum: 5e+3aergo")}, } - for _, tt := range tests { - result, err := transformAmount(tt.amountStr) + for version := min_version; version <= max_version; version++ { + for _, tt := range tests { + result, err := transformAmount(tt.amountStr, version) + + 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) + } + } + + // now in uppercase + result, err = transformAmount(strings.ToUpper(tt.amountStr), version) + + if tt.expectedError != nil { + if assert.Error(t, err, "Expected error: %s", tt.expectedError.Error()) { + assert.Equal(t, strings.ToUpper(tt.expectedError.Error()), strings.ToUpper(err.Error())) + } + } else { + if assert.NoError(t, err) && tt.expectedAmount != nil { + assert.Equal(t, tt.expectedAmount, result) + } + } + } + } + + // Define the test cases for amounts in decimal format + decimal_tests := []struct { + forkVersion int32 + amountStr string + expectedAmount *big.Int + expectedError error + }{ + // V3 - decimal amounts not supported + {3, "123.456", nil, errors.New("converting error for Integer: 123.456")}, + {3, "123.456 aergo", nil, errors.New("converting error for BigNum: 123.456 aergo")}, + {3, ".1", nil, errors.New("converting error for Integer: .1")}, + {3, ".1aergo", nil, errors.New("converting error for BigNum: .1aergo")}, + {3, ".1 aergo", nil, errors.New("converting error for BigNum: .1 aergo")}, + {3, ".10", nil, errors.New("converting error for Integer: .10")}, + // V4 - decimal amounts supported + {4, "123.456aergo", bigIntFromString("123456000000000000000"), nil}, + {4, "123.4aergo", bigIntFromString("123400000000000000000"), nil}, + {4, "123.aergo", bigIntFromString("123000000000000000000"), nil}, + {4, "100.aergo", bigIntFromString("100000000000000000000"), nil}, + {4, "10.aergo", bigIntFromString("10000000000000000000"), nil}, + {4, "1.aergo", bigIntFromString("1000000000000000000"), nil}, + {4, "100.0aergo", bigIntFromString("100000000000000000000"), nil}, + {4, "10.0aergo", bigIntFromString("10000000000000000000"), nil}, + {4, "1.0aergo", bigIntFromString("1000000000000000000"), nil}, + {4, ".1aergo", bigIntFromString("100000000000000000"), nil}, + {4, "0.1aergo", bigIntFromString("100000000000000000"), nil}, + {4, ".01aergo", bigIntFromString("10000000000000000"), nil}, + {4, "0.01aergo", bigIntFromString("10000000000000000"), nil}, + {4, "0.0000000001aergo", bigIntFromString("100000000"), nil}, + {4, "0.000000000000000001aergo", bigIntFromString("1"), nil}, + {4, "0.000000000000000123aergo", bigIntFromString("123"), nil}, + {4, "0.000000000000000000aergo", bigIntFromString("0"), nil}, + {4, "0.000000000000123000aergo", bigIntFromString("123000"), nil}, + {4, "0.100000000000000123aergo", bigIntFromString("100000000000000123"), nil}, + {4, "1.000000000000000123aergo", bigIntFromString("1000000000000000123"), nil}, + {4, "123.456000000000000789aergo", bigIntFromString("123456000000000000789"), nil}, + + {4, "123.456 aergo", bigIntFromString("123456000000000000000"), nil}, + {4, "123.4 aergo", bigIntFromString("123400000000000000000"), nil}, + {4, "123. aergo", bigIntFromString("123000000000000000000"), nil}, + {4, "100. aergo", bigIntFromString("100000000000000000000"), nil}, + {4, "10. aergo", bigIntFromString("10000000000000000000"), nil}, + {4, "1. aergo", bigIntFromString("1000000000000000000"), nil}, + {4, "100.0 aergo", bigIntFromString("100000000000000000000"), nil}, + {4, "10.0 aergo", bigIntFromString("10000000000000000000"), nil}, + {4, "1.0 aergo", bigIntFromString("1000000000000000000"), nil}, + {4, ".1 aergo", bigIntFromString("100000000000000000"), nil}, + {4, "0.1 aergo", bigIntFromString("100000000000000000"), nil}, + {4, ".01 aergo", bigIntFromString("10000000000000000"), nil}, + {4, "0.01 aergo", bigIntFromString("10000000000000000"), nil}, + {4, "0.0000000001 aergo", bigIntFromString("100000000"), nil}, + {4, "0.000000000000000001 aergo", bigIntFromString("1"), nil}, + {4, "0.000000000000000123 aergo", bigIntFromString("123"), nil}, + {4, "0.000000000000000000 aergo", bigIntFromString("0"), nil}, + {4, "0.000000000000123000 aergo", bigIntFromString("123000"), nil}, + {4, "0.100000000000000123 aergo", bigIntFromString("100000000000000123"), nil}, + {4, "1.000000000000000123 aergo", bigIntFromString("1000000000000000123"), nil}, + {4, "123.456000000000000789 aergo", bigIntFromString("123456000000000000789"), nil}, + + {4, "0.0000000000000000001aergo", nil, errors.New("converting error for BigNum: 0.0000000000000000001aergo")}, + {4, "0.000000000000000000000000001aergo", nil, errors.New("converting error for BigNum: 0.000000000000000000000000001aergo")}, + {4, "0.000000000000000123000aergo", nil, errors.New("converting error for BigNum: 0.000000000000000123000aergo")}, + {4, "0.0000000000000000000000aergo", nil, errors.New("converting error for BigNum: 0.0000000000000000000000aergo")}, + + {4, "0.0000000000000000001 aergo", nil, errors.New("converting error for BigNum: 0.0000000000000000001 aergo")}, + {4, "0.000000000000000000000000001 aergo", nil, errors.New("converting error for BigNum: 0.000000000000000000000000001 aergo")}, + {4, "0.000000000000000123000 aergo", nil, errors.New("converting error for BigNum: 0.000000000000000123000 aergo")}, + {4, "0.0000000000000000000000 aergo", nil, errors.New("converting error for BigNum: 0.0000000000000000000000 aergo")}, + } + + for _, tt := range decimal_tests { + result, err := transformAmount(tt.amountStr, tt.forkVersion) + + if tt.expectedError != nil { + if assert.Error(t, err, "Expected error: %s", tt.expectedError.Error()) { + assert.Equal(t, tt.expectedError.Error(), err.Error(), tt.amountStr) + } + } else { + if assert.NoError(t, err) && tt.expectedAmount != nil { + assert.Equal(t, tt.expectedAmount, result, tt.amountStr) + } + } + + // now in uppercase + result, err = transformAmount(strings.ToUpper(tt.amountStr), tt.forkVersion) if tt.expectedError != nil { if assert.Error(t, err, "Expected error: %s", tt.expectedError.Error()) { - assert.Equal(t, tt.expectedError.Error(), err.Error()) + assert.Equal(t, strings.ToUpper(tt.expectedError.Error()), strings.ToUpper(err.Error()), tt.amountStr) } } else { if assert.NoError(t, err) && tt.expectedAmount != nil { - assert.Equal(t, tt.expectedAmount, result) + assert.Equal(t, tt.expectedAmount, result, tt.amountStr) } } } diff --git a/contract/vm_dummy/test_files/bignum_values.lua b/contract/vm_dummy/test_files/bignum_values.lua new file mode 100644 index 000000000..7c31db65a --- /dev/null +++ b/contract/vm_dummy/test_files/bignum_values.lua @@ -0,0 +1,7 @@ + +function parse_bignum(value) + local val = bignum.number(value) + return bignum.tostring(val) +end + +abi.register(parse_bignum) diff --git a/contract/vm_dummy/test_files/contract_system.lua b/contract/vm_dummy/test_files/contract_system.lua index 88383efc7..b5577ef67 100644 --- a/contract/vm_dummy/test_files/contract_system.lua +++ b/contract/vm_dummy/test_files/contract_system.lua @@ -1,7 +1,20 @@ function testState() system.setItem("key1", 999) - return system.getSender(), system.getTxhash(), system.getContractID(), system.getTimestamp(), system.getBlockheight(), - system.getItem("key1") + return system.getSender(), system.getTxhash(), system.getContractID(), system.getTimestamp(), system.getBlockheight(), system.getItem("key1") end abi.register(testState) + +function get_version() + return system.version() +end + +function to_address(pubkey) + return system.toAddress(pubkey) +end + +function to_pubkey(address) + return system.toPubKey(address) +end + +abi.register_view(get_version, to_address, to_pubkey) diff --git a/contract/vm_dummy/test_files/disabled-functions.lua b/contract/vm_dummy/test_files/disabled-functions.lua new file mode 100644 index 000000000..9f57b6c14 --- /dev/null +++ b/contract/vm_dummy/test_files/disabled-functions.lua @@ -0,0 +1,46 @@ + +function check_disabled_functions() + + -- check the disabled modules + assert(os == nil, "os is available") + assert(io == nil, "io is available") + assert(debug == nil, "debug is available") + assert(jit == nil, "jit is available") + assert(ffi == nil, "ffi is available") + assert(coroutine == nil, "coroutine is available") + assert(package == nil, "package is available") + + -- check the disabled functions + assert(collectgarbage == nil, "collectgarbage is available") + assert(gcinfo == nil, "gcinfo is available") + assert(module == nil, "module is available") + assert(require == nil, "require is available") + assert(dofile == nil, "dofile is available") + assert(load == nil, "load is available") + assert(loadlib == nil, "loadlib is available") + assert(loadfile == nil, "loadfile is available") + assert(loadstring == nil, "loadstring is available") + assert(print == nil, "print is available") + assert(getmetatable == nil, "getmetatable is available") + assert(setmetatable == nil, "setmetatable is available") + assert(rawget == nil, "rawget is available") + assert(rawset == nil, "rawset is available") + assert(rawequal == nil, "rawequal is available") + assert(string.dump == nil, "string.dump is available") + + local success, result = pcall(function() newproxy() end) + assert(success == false and result:match(".* 'newproxy' not supported"), "newproxy is available") + local success, result = pcall(function() setfenv() end) + assert(success == false and result:match(".* 'setfenv' not supported"), "setfenv is available") + local success, result = pcall(function() getfenv() end) + assert(success == false and result:match(".* 'getfenv' not supported"), "getfenv is available") + + -- make sure the tostring does not return a pointer + local tab = {} + assert(not tostring(type):match("0x[%x]+"), "tostring returns a pointer for function") + assert(not tostring(system):match("0x[%x]+"), "tostring returns a pointer for internal table") + assert(not tostring(tab):match("0x[%x]+"), "tostring returns a pointer for table") + +end + +abi.register(check_disabled_functions) diff --git a/contract/vm_dummy/test_files/feature_isolation.lua b/contract/vm_dummy/test_files/feature_isolation.lua index 84fd6abb6..2244a9a61 100644 --- a/contract/vm_dummy/test_files/feature_isolation.lua +++ b/contract/vm_dummy/test_files/feature_isolation.lua @@ -22,8 +22,10 @@ function override_functions() -- override contract.balance contract.balance = function() return "123" end - -- override the __add metamethod on bignum module - getmetatable(bignum.number(0)).__add = function(x,y) return x-y end + if getmetatable ~= nil then + -- override the __add metamethod on bignum module + getmetatable(bignum.number(0)).__add = function(x,y) return x-y end + end end @@ -37,7 +39,9 @@ function check_local_overridden_functions() assert2(test_sender() == "overridden", "system.getSender() override failed") assert2(test_origin() == "overridden", "system.getOrigin() override failed") assert2(test_balance() == "123", "contract.balance() override failed") - assert2(test_bignum() == bignum.number(3), "metamethod override failed") + if getmetatable ~= nil then + assert2(test_bignum() == bignum.number(3), "metamethod override failed") + end end diff --git a/contract/vm_dummy/test_files/feature_pcall_rollback_4.lua b/contract/vm_dummy/test_files/feature_pcall_rollback_4a.lua similarity index 100% rename from contract/vm_dummy/test_files/feature_pcall_rollback_4.lua rename to contract/vm_dummy/test_files/feature_pcall_rollback_4a.lua diff --git a/contract/vm_dummy/test_files/feature_pcall_rollback_4b.lua b/contract/vm_dummy/test_files/feature_pcall_rollback_4b.lua new file mode 100644 index 000000000..bc1c40929 --- /dev/null +++ b/contract/vm_dummy/test_files/feature_pcall_rollback_4b.lua @@ -0,0 +1,128 @@ +state.var { + resolver = state.value(), + name = state.value(), + values = state.map() +} + +function constructor(resolver_address, contract_name) + -- initialize state variables + resolver:set(resolver_address) + name:set(contract_name) + -- initialize db + db.exec("create table config (value integer primary key) without rowid") + db.exec("insert into config values (0)") + db.exec([[create table products ( + id integer primary key, + name text not null, + price real) + ]]) + db.exec("insert into products (name,price) values ('first', 1234.56)") +end + +function resolve(name) + return contract.call(resolver:get(), "resolve", name) +end + +--[[ + ['set','x',111], + ['pcall','A'] +],[ + ['set','x',222], + ['pcall','A'], + ['fail'] +],[ + ['set','x',333] +]] + +function test(script) + -- get the commands for this function to execute + local commands = table.remove(script, 1) + + -- execute each command + for i, cmd in ipairs(commands) do + local action = cmd[1] + if action == "set" then + contract.event(name:get() .. ".set", cmd[2], cmd[3]) + values[cmd[2]] = cmd[3] + elseif action == "pcall" then + local to_call = cmd[2] + local amount = cmd[3] + if to_call == name:get() then + pcall(test, script) + elseif amount ~= nil then + contract.event(name:get() .. " send " .. to_call, amount) + local address = resolve(to_call) + success, ret = pcall(function() + return contract.call.value(amount)(address, "test", script) + end) + else + local address = resolve(to_call) + success, ret = pcall(contract.call, address, "test", script) + end + --contract.event("result", success, ret) + elseif action == "send" then + local to = cmd[2] + contract.event(name:get() .. " send " .. to, amount) + contract.send(resolve(to), cmd[3]) + elseif action == "deploy" then + local code_or_address = resolve_deploy(cmd[2]) + pcall(contract.deploy, code_or_address, unpack(cmd,3)) + elseif action == "deploy.send" then + contract.event(name:get() .. ".deploy.send", amount) + local code_or_address = resolve_deploy(cmd[3]) + pcall(function() + contract.deploy.value(cmd[2])(code_or_address, unpack(cmd,4)) + end) + elseif action == "db.set" then + db.exec("update config set value = " .. cmd[2]) + elseif action == "db.insert" then + db.exec("insert into products (name,price) values ('another',1234.56)") + elseif action == "fail" then + assert(1 == 0, "failed") + end + end + +end + +function set(key, value) + values[key] = value +end + +function get(key) + return values[key] +end + +function db_reset() + db.exec("update config set value = 0") + db.exec("delete from products where id > 1") +end + +function db_get() + local rs = db.query("select value from config") + if rs:next() then + return rs:get() + else + return "error" + end +end + +function db_count() + local rs = db.query("select count(*) from products") + if rs:next() then + return rs:get() + else + return "error" + end +end + +function default() + -- only receive +end + +function send_back(to) + contract.send(resolve(to), contract.balance()) +end + +abi.payable(constructor, test, default) +abi.register(set, send_back, db_reset) +abi.register_view(get, db_get, db_count) diff --git a/contract/vm_dummy/test_files/feature_pcall_rollback_4c.lua b/contract/vm_dummy/test_files/feature_pcall_rollback_4c.lua new file mode 100644 index 000000000..4e0ed37f2 --- /dev/null +++ b/contract/vm_dummy/test_files/feature_pcall_rollback_4c.lua @@ -0,0 +1,132 @@ +state.var { + resolver = state.value(), + name = state.value(), + values = state.map() +} + +function constructor(resolver_address, contract_name) + -- initialize state variables + resolver:set(resolver_address) + name:set(contract_name) + -- initialize db + db.exec("create table config (value integer primary key) without rowid") + db.exec("insert into config values (0)") + db.exec([[create table products ( + id integer primary key, + name text not null, + price real) + ]]) + db.exec("insert into products (name,price) values ('first', 1234.56)") +end + +function resolve(name) + return contract.call(resolver:get(), "resolve", name) +end + +function error_handler(err_msg) + return "oh no! " .. err_msg +end + +--[[ + ['set','x',111], + ['pcall','A'] +],[ + ['set','x',222], + ['pcall','A'], + ['fail'] +],[ + ['set','x',333] +]] + +function test(script) + -- get the commands for this function to execute + local commands = table.remove(script, 1) + + -- execute each command + for i, cmd in ipairs(commands) do + local action = cmd[1] + if action == "set" then + contract.event(name:get() .. ".set", cmd[2], cmd[3]) + values[cmd[2]] = cmd[3] + elseif action == "pcall" then + local to_call = cmd[2] + local amount = cmd[3] + if to_call == name:get() then + xpcall(test, error_handler, script) + elseif amount ~= nil then + contract.event(name:get() .. " send " .. to_call, amount) + local address = resolve(to_call) + success, ret = xpcall(function() + return contract.call.value(amount)(address, "test", script) + end, error_handler) + else + local address = resolve(to_call) + success, ret = xpcall(contract.call, error_handler, address, "test", script) + end + --contract.event("result", success, ret) + elseif action == "send" then + local to = cmd[2] + contract.event(name:get() .. " send " .. to, amount) + contract.send(resolve(to), cmd[3]) + elseif action == "deploy" then + local code_or_address = resolve_deploy(cmd[2]) + xpcall(contract.deploy, error_handler, code_or_address, unpack(cmd,3)) + elseif action == "deploy.send" then + contract.event(name:get() .. ".deploy.send", amount) + local code_or_address = resolve_deploy(cmd[3]) + xpcall(function() + contract.deploy.value(cmd[2])(code_or_address, unpack(cmd,4)) + end, error_handler) + elseif action == "db.set" then + db.exec("update config set value = " .. cmd[2]) + elseif action == "db.insert" then + db.exec("insert into products (name,price) values ('another',1234.56)") + elseif action == "fail" then + assert(1 == 0, "failed") + end + end + +end + +function set(key, value) + values[key] = value +end + +function get(key) + return values[key] +end + +function db_reset() + db.exec("update config set value = 0") + db.exec("delete from products where id > 1") +end + +function db_get() + local rs = db.query("select value from config") + if rs:next() then + return rs:get() + else + return "error" + end +end + +function db_count() + local rs = db.query("select count(*) from products") + if rs:next() then + return rs:get() + else + return "error" + end +end + +function default() + -- only receive +end + +function send_back(to) + contract.send(resolve(to), contract.balance()) +end + +abi.payable(constructor, test, default) +abi.register(set, send_back, db_reset) +abi.register_view(get, db_get, db_count) diff --git a/contract/vm_dummy/test_files/gas_bf.lua b/contract/vm_dummy/test_files/gas_bf_v2.lua similarity index 100% rename from contract/vm_dummy/test_files/gas_bf.lua rename to contract/vm_dummy/test_files/gas_bf_v2.lua diff --git a/contract/vm_dummy/test_files/gas_bf_v4.lua b/contract/vm_dummy/test_files/gas_bf_v4.lua new file mode 100644 index 000000000..16deb499e --- /dev/null +++ b/contract/vm_dummy/test_files/gas_bf_v4.lua @@ -0,0 +1,330 @@ +loop_cnt = 1000 +arr = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 } +tbl = { name = "user2", year = 1981, age = 41 } +long_str = +[[Looks for the first match of pattern in the string s. If it finds a match, then find returns the indices of s where this occurrence starts and ends; otherwise, it returns nil. A third, optional numerical argument init specifies where to start the search; its default value is 1 and can be negative. A value of true as a fourth, optional argument plain turns off the pattern matching facilities, so the function does a plain "find substring" operation, with no characters in pattern being considered "magic". Note that if plain is given, then init must be given as well. +]] +long_str1 = +[[Looks for the first match of pattern in the string s. If it finds a match, then find returns the indices of s where this occurrence starts and ends; otherwise, it returns nil. A third, optional numerical argument init specifies where to start the search; its default value is 1 and can be negative. A value of true as a fourth, optional argument plain turns off the pattern matching facilities, so the function does a plain "find substring" operation, with no characters in pattern being considered "magic". Note that if plain is given, then init must be given as werr. +]] + +function copy_arr(arr) + local a = {} + for i, v in ipairs(arr) do + a[i] = v + end + return a +end + +function m1k(fn, ...) + for i = 1, loop_cnt do + fn(...) + end +end + +function basic_fns() + m1k(assert, true, 'true') + m1k(assert, 1 == 1, 'true') + m1k(assert, 1 ~= 2, 'true') + m1k(assert, long_str == long_str, 'true') + m1k(assert, long_str ~= long_str1, 'true') + local x = { value = 5 } + m1k(ipairs, arr) + m1k(next, tbl) + m1k(next, tbl, "year") + m1k(next, tbl, "name") + m1k(pairs, tbl) + m1k(select, '#', 'a', 'b', 'c', 'd') + m1k(select, '#', arr) + m1k(select, '2', 'a', 'b', 'c', 'd') + m1k(select, '2', arr) + m1k(select, '6', arr) + m1k(select, '9', arr) + m1k(tonumber, 0x10, 16) + m1k(tonumber, '112134', 16) + m1k(tonumber, '112134') + m1k(tonumber, 112134) + m1k(tostring, 'i am a string') + m1k(tostring, 1) + m1k(tostring, 112134) + m1k(tostring, true) + m1k(tostring, nil) + m1k(tostring, 3.14) + m1k(tostring, 3.14159267) + m1k(tostring, x) + m1k(type, '112134') + m1k(type, 112134) + m1k(type, {}) + m1k(type, type) + m1k(unpack, { 1, 2, 3, 4, 5 }, 2, 4) + m1k(unpack, arr, 2, 4) + m1k(unpack, { 1, 2, 3, 4, 5 }) + m1k(unpack, arr) + local a = {} + for i = 1, 100 do + a[i] = i * i + end + m1k(unpack, a, 2, 4) + m1k(unpack, a, 10, 40) + m1k(unpack, a) +end + +function string_fns() + m1k(string.byte, "hello string", 3, 7) + m1k(string.byte, long_str, 3, 7) + m1k(string.byte, long_str, 1, #long_str) + m1k(string.char, 72, 101, 108, 108, 111) + m1k(string.char, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100) + m1k(string.char, string.byte(long_str, 1, #long_str)) + m1k(string.find, long_str, "nume.....") + m1k(string.find, long_str, "we..") + m1k(string.find, long_str, "wi..") + m1k(string.find, long_str, "pattern") + m1k(string.find, long_str, "pattern", 200) + m1k(string.find, "hello world hellow", "hello") + m1k(string.find, "hello world hellow", "hello", 3) + m1k(string.find, long_str, "head") + m1k(string.format, "string.format %d, %.9f, %e, %g: %s", 1, 1.999999999, 10e9, 3.14, "end of string") + m1k(string.format, "string.format %d, %.9f, %e", 1, 1.999999999, 10e9) + s = "hello world from Lua" + m1k(string.gmatch, s, "%a+") + m1k(function() + for w in string.gmatch(s, "%a+") do + end + end) + m1k(string.gsub, s, "(%w+)", "%1 %1") + s2 = s .. ' ' .. s + m1k(string.gsub, s2, "(%w+)", "%1 %1") + m1k(string.gsub, s, "(%w+)%s*(%w+)", "%2 %1") + m1k(string.len, s) + m1k(string.len, s2) + m1k(string.len, long_str) + m1k(string.lower, s) + m1k(string.lower, long_str) + m1k(string.match, s, "(L%w+)") + m1k(string.rep, s, 2) + m1k(string.rep, s, 4) + m1k(string.rep, s, 8) + m1k(string.rep, s, 16) + m1k(string.rep, long_str, 16) + m1k(string.reverse, s) + m1k(string.reverse, s2) + m1k(string.reverse, long_str) + m1k(string.sub, s, 10, 13) + m1k(string.sub, s, 10, -3) + m1k(string.sub, long_str, 10, 13) + m1k(string.sub, long_str, 10, -3) + m1k(string.upper, s) + m1k(string.upper, s2) + m1k(string.upper, long_str) +end + +function table_fns1() + local a = copy_arr(arr) + local a100 = {} + for i = 1, 100 do + a100[i] = i * i + end + m1k(table.concat, a, ',') + m1k(table.concat, a, ',', 3, 7) + m1k(table.concat, a100, ',') + m1k(table.concat, a100, ',', 3, 7) + m1k(table.concat, a100, ',', 3, 32) + local as10 = {} + for i = 1, 10 do + as10[i] = "hello" + end + local as100 = {} + for i = 1, 100 do + as100[i] = "hello" + end + m1k(table.concat, as10, ',') + m1k(table.concat, as10, ',', 3, 7) + m1k(table.concat, as100, ',') + m1k(table.concat, as100, ',', 3, 7) + m1k(table.concat, as100, ',', 3, 32) + for i = 1, 10 do + as10[i] = "h" + end + for i = 1, 100 do + as100[i] = "h" + end + m1k(table.concat, as10, ',') + m1k(table.concat, as10, ',', 3, 7) + m1k(table.concat, as100, ',') + m1k(table.concat, as100, ',', 3, 7) + m1k(table.concat, as100, ',', 3, 32) +end + +function table_fns2() + local a = copy_arr(arr) + local a100 = {} + for i = 1, 100 do + a100[i] = i * i + end + m1k(table.insert, a, 11) + for i = 1, 1000 do + table.remove(a) + end + m1k(table.insert, a, 5, 11) + for i = 1, 1000 do + table.remove(a, 5) + end + --m1k(table.insert, a, 5, -5) + --m1k(table.insert, a, 1, -5) + m1k(table.insert, a100, 11) + for i = 1, 1000 do + table.remove(a100) + end + m1k(table.insert, a100, 5, -5) + for i = 1, 1000 do + table.remove(a100, 5) + end + --m1k(table.insert, a100, 1, -5) +end + +function table_fns3() + local a = copy_arr(arr) + local a100 = {} + for i = 1, 100 do + a100[i] = i * i + end + m1k(table.maxn, a) + m1k(table.maxn, a100) + for i = 1, 1000 do + table.insert(a, i) + end + m1k(table.remove, a) + for i = 1, 1000 do + table.insert(a, 5, i) + end + m1k(table.remove, a, 5) + for i = 1, 1000 do + table.insert(a100, i) + end + m1k(table.remove, a100) + for i = 1, 1000 do + table.insert(a100, 5, i) + end + m1k(table.remove, a100, 5) +end + +function table_fns4() + local a = copy_arr(arr) + local a100 = {} + for i = 1, 100 do + a100[i] = i * i + end + m1k(table.sort, a) + m1k(table.sort, a, function(x, y) return x > y end) + m1k(table.sort, a100) + m1k(table.sort, a100, function(x, y) return x > y end) +end + +function math_fns() + d = {} + for i = 1, loop_cnt do + d[i] = -500 + i + end + for i = 1, loop_cnt, 10 do + d[i] = d[i] + 0.5 + end + for i = 1, loop_cnt, 13 do + d[i] = d[i] + 0.3 + end + for i = 1, loop_cnt, 17 do + d[i] = d[i] + 0.7 + end + f = {} + for i = 1, loop_cnt do + f[i] = -1 + i * 0.002 + end + local md = function(fn, d, ...) + for i, v in ipairs(d) do + fn(v, ...) + end + end + md(math.abs, d) + md(math.ceil, d) + md(math.ceil, f) + md(math.floor, d) + md(math.floor, f) + local filter = function(l, cond) + r = {} + for i, v in ipairs(l) do + if cond(v) then + table.insert(r, v) + end + end + return r + end + ud = filter(d, function(v) return v >= 0 end) + uf = filter(f, function(v) return v >= 0 end) + m1k(math.max, unpack(ud)) + m1k(math.max, unpack(d)) + m1k(math.max, unpack(uf)) + m1k(math.max, unpack(f)) + m1k(math.min, unpack(ud)) + m1k(math.min, unpack(d)) + m1k(math.min, unpack(uf)) + m1k(math.min, unpack(f)) + md(math.pow, d, 2) + md(math.pow, d, 4) + md(math.pow, d, 8) + md(math.pow, f, 2) + md(math.pow, f, 4) + md(math.pow, f, 8) + md(math.pow, ud, 8.4) + md(math.pow, uf, 8.4) +end + +function bit_fns() + m1k(bit.tobit, 0xffffffff) + m1k(bit.tobit, 0xffffffff + 1) + m1k(bit.tobit, 2 ^ 40 + 1234) + m1k(bit.tohex, 1) + m1k(bit.tohex, -1) + m1k(bit.tohex, -1, -8) + m1k(bit.tohex, 0x87654321, 4) + m1k(bit.bnot, 0) + m1k(bit.bnot, 0x12345678) + m1k(bit.bor, 1) + m1k(bit.bor, 1, 2) + m1k(bit.bor, 1, 2, 4) + m1k(bit.bor, 1, 2, 4, 8) + m1k(bit.band, 0x12345678, 0xff) + m1k(bit.band, 0x12345678, 0xff, 0x3f) + m1k(bit.band, 0x12345678, 0xff, 0x3f, 0x1f) + m1k(bit.bxor, 0xa5a5f0f0, 0xaa55ff00) + m1k(bit.bxor, 0xa5a5f0f0, 0xaa55ff00, 0x18000000) + m1k(bit.bxor, 0xa5a5f0f0, 0xaa55ff00, 0x18000000, 0x00000033) + m1k(bit.lshift, 1, 0) + m1k(bit.lshift, 1, 8) + m1k(bit.lshift, 1, 40) + m1k(bit.rshift, 256, 0) + m1k(bit.rshift, 256, 8) + m1k(bit.rshift, 256, 40) + m1k(bit.arshift, 0x87654321, 0) + m1k(bit.arshift, 0x87654321, 12) + m1k(bit.arshift, 0x87654321, 40) + m1k(bit.rol, 0x12345678, 0) + m1k(bit.rol, 0x12345678, 12) + m1k(bit.rol, 0x12345678, 40) + m1k(bit.ror, 0x12345678, 0) + m1k(bit.ror, 0x12345678, 12) + m1k(bit.ror, 0x12345678, 40) + m1k(bit.bswap, 0x12345678) +end + +function main() + basic_fns() + string_fns() + table_fns1() + table_fns2() + table_fns3() + table_fns4() + math_fns() + bit_fns() +end + +abi.register(main) diff --git a/contract/vm_dummy/test_files/pcall-events-0.lua b/contract/vm_dummy/test_files/pcall-events-0.lua new file mode 100644 index 000000000..8979716e3 --- /dev/null +++ b/contract/vm_dummy/test_files/pcall-events-0.lua @@ -0,0 +1,38 @@ +state.var { + parent = state.value() +} + +function constructor(address) + parent:set(address) +end + +function error_handler(err_msg) + return "oh no! " .. err_msg +end + +function test_pcall() + local s, r = pcall(do_work, parent:get(), "pcall") + assert(s == false, "call not failed") + return r +end + +function test_xpcall() + local s, r = xpcall(do_work, error_handler, parent:get(), "xpcall") + assert(s == false, "call not failed") + return r +end + +function test_contract_pcall() + local s, r = contract.pcall(do_work, parent:get(), "contract.pcall") + assert(s == false, "call not failed") + return r +end + +function do_work(contract_address, caller) + contract.event("inside " .. caller .. " before") + local r = contract.call(contract_address, "call_me") + contract.event("inside " .. caller .. " after") + return r +end + +abi.register(test_pcall, test_xpcall, test_contract_pcall) diff --git a/contract/vm_dummy/test_files/pcall-events-1.lua b/contract/vm_dummy/test_files/pcall-events-1.lua new file mode 100644 index 000000000..e1096c7b4 --- /dev/null +++ b/contract/vm_dummy/test_files/pcall-events-1.lua @@ -0,0 +1,44 @@ +state.var { + parent = state.value() +} + +function constructor(address) + parent:set(address) +end + +function error_handler(err_msg) + return "oh no! " .. err_msg +end + +function test_pcall() + contract.event("before pcall") + local s, r = pcall(do_work, parent:get(), "pcall") + contract.event("after pcall") + assert(s == false, "call not failed") + return r +end + +function test_xpcall() + contract.event("before xpcall") + local s, r = xpcall(do_work, error_handler, parent:get(), "xpcall") + contract.event("after xpcall") + assert(s == false, "call not failed") + return r +end + +function test_contract_pcall() + contract.event("before contract.pcall") + local s, r = contract.pcall(do_work, parent:get(), "contract.pcall") + contract.event("after contract.pcall") + assert(s == false, "call not failed") + return r +end + +function do_work(contract_address, caller) + contract.event("inside " .. caller .. " before") + local r = contract.call(contract_address, "call_me") + contract.event("inside " .. caller .. " after") + return r +end + +abi.register(test_pcall, test_xpcall, test_contract_pcall) diff --git a/contract/vm_dummy/test_files/pcall-events-2.lua b/contract/vm_dummy/test_files/pcall-events-2.lua new file mode 100644 index 000000000..03a6b793b --- /dev/null +++ b/contract/vm_dummy/test_files/pcall-events-2.lua @@ -0,0 +1,19 @@ +state.var { + parent = state.value() +} + +function constructor(address) + parent:set(address) +end + +function call_me() + contract.event("contract-2 before call") + -- call contract-3 + local result = contract.call(parent:get(), "call_me") + contract.event("contract-2 after call") + -- raises an error: + assert(1 == 0) + contract.event("contract-2 returning") +end + +abi.register(call_me) diff --git a/contract/vm_dummy/test_files/pcall-events-3.lua b/contract/vm_dummy/test_files/pcall-events-3.lua new file mode 100644 index 000000000..53de91685 --- /dev/null +++ b/contract/vm_dummy/test_files/pcall-events-3.lua @@ -0,0 +1,5 @@ +function call_me() + contract.event("contract-3") +end + +abi.register(call_me) diff --git a/contract/vm_dummy/test_files/type_bignum.lua b/contract/vm_dummy/test_files/type_bignum.lua index 280392b9e..a6c1d9647 100644 --- a/contract/vm_dummy/test_files/type_bignum.lua +++ b/contract/vm_dummy/test_files/type_bignum.lua @@ -43,8 +43,24 @@ function calcBignum() bg6 = bignum.number(n1) assert(bg3 == bg4 and bg4 == bg5) bg5 = bg1 - bg3 - assert(bignum.isneg(bg5) and bg5 == bignum.neg(bg1)) system.print(bg3, bg5, bg6) + --if system.version() >= 4 then + if type(bignum.ispositive) == "function" then + -- ispositive() and isnegative() and iszero() + assert(bignum.isnegative(bg5) and bg5 == bignum.neg(bg1)) + assert(bignum.ispositive(bg1) and bignum.ispositive(bg3)) + assert(not bignum.ispositive(bg5) and not bignum.iszero(bg5)) + assert(not bignum.isnegative(bg1) and not bignum.iszero(bg1)) + -- ispositive() and isneg() and iszero() + assert(bignum.isneg(bg5) and bg5 == bignum.neg(bg1)) + assert(bignum.ispositive(bg1) and bignum.ispositive(bg3)) + assert(not bignum.ispositive(bg5) and not bignum.iszero(bg5)) + assert(not bignum.isneg(bg1) and not bignum.iszero(bg1)) + else + -- isneg() and iszero() + assert(bignum.isneg(bg5) and bg5 == bignum.neg(bg1)) + assert(not bignum.isneg(bg1) and not bignum.iszero(bg1)) + end bg6 = bignum.number(1) assert(bg6 > bg5) a = bignum.number(2) diff --git a/contract/vm_dummy/test_files/type_sparsetable.lua b/contract/vm_dummy/test_files/type_sparsetable.lua index bc6f9173b..b0c404cab 100644 --- a/contract/vm_dummy/test_files/type_sparsetable.lua +++ b/contract/vm_dummy/test_files/type_sparsetable.lua @@ -1,12 +1,10 @@ -function is_table_equal(t1, t2, ignore_mt) +function is_table_equal(t1, t2) local ty1 = type(t1) local ty2 = type(t2) if ty1 ~= ty2 then return false end -- non-table types can be directly compared if ty1 ~= 'table' and ty2 ~= 'table' then return t1 == t2 end - -- as well as tables which have the metamethod __eq - local mt = getmetatable(t1) - if not ignore_mt and mt and mt.__eq then return t1 == t2 end + -- if table, compare each key-value pair for k1, v1 in pairs(t1) do local v2 = t2[k1] if v2 == nil or not is_table_equal(v1, v2) then return false end @@ -23,7 +21,7 @@ function r() t[10000] = "1234" system.setItem("k", t) k = system.getItem("k") - if is_table_equal(t, k, false) then + if is_table_equal(t, k) then return 1 end return 0 diff --git a/contract/vm_dummy/vm_dummy.go b/contract/vm_dummy/vm_dummy.go index 73f8739cb..b867941ad 100644 --- a/contract/vm_dummy/vm_dummy.go +++ b/contract/vm_dummy/vm_dummy.go @@ -20,6 +20,7 @@ import ( "github.com/aergoio/aergo/v2/contract" "github.com/aergoio/aergo/v2/contract/system" "github.com/aergoio/aergo/v2/fee" + "github.com/aergoio/aergo/v2/internal/enc/hex" "github.com/aergoio/aergo/v2/internal/enc/base58" "github.com/aergoio/aergo/v2/state" "github.com/aergoio/aergo/v2/state/statedb" @@ -409,6 +410,7 @@ func hash(id uint64) []byte { type luaTxDeploy struct { luaTxContractCommon + isCompiled bool cErr error } @@ -418,6 +420,35 @@ func NewLuaTxDeploy(sender, recipient string, amount uint64, code string) *luaTx return NewLuaTxDeployBig(sender, recipient, types.NewAmount(amount, types.Aer), code) } +func NewLuaTxDeployBig(sender, recipient string, amount *big.Int, code string) *luaTxDeploy { + var payload []byte + var isCompiled bool + var err error + + if hex.IsHexString(code) { + payload, err = hex.Decode(code) + if err != nil { + return &luaTxDeploy{cErr: err} + } + isCompiled = true + } else { + payload = util.NewLuaCodePayload([]byte(code), nil) + isCompiled = false + } + + return &luaTxDeploy{ + luaTxContractCommon: luaTxContractCommon{ + _sender: contract.StrHash(sender), + _recipient: contract.StrHash(recipient), + _payload: payload, + _amount: amount, + txId: newTxId(), + }, + isCompiled: isCompiled, + cErr: nil, + } +} + /* func contract.StrHash(d string) []byte { // using real address @@ -446,7 +477,7 @@ func (l *luaTxDeploy) okMsg() string { } func (l *luaTxDeploy) Constructor(args string) *luaTxDeploy { - if len(args) == 0 || strings.Compare(args, "[]") == 0 || l.cErr != nil { + if len(args) == 0 || strings.Compare(args, "[]") == 0 || l.isCompiled || l.cErr != nil { return l } l._payload = util.NewLuaCodePayload(util.LuaCodePayload(l._payload).Code(), []byte(args)) @@ -558,6 +589,16 @@ func (l *luaTxDeploy) run(execCtx context.Context, bs *state.BlockState, bc *Dum if l.cErr != nil { return l.cErr } + if bc.HardforkVersion < 4 && !l.isCompiled { + // compile the plain code to bytecode + payload := util.LuaCodePayload(l._payload) + code := string(payload.Code()) + byteCode, err := contract.Compile(code, nil) + if err != nil { + return err + } + l._payload = util.NewLuaCodePayload(byteCode, payload.Args()) + } return contractFrame(l, bs, bc, receiptTx, func(sender, contractV *state.AccountState, contractId types.AccountID, eContractState *statedb.ContractState) (string, []*types.Event, *big.Int, error) { contractV.State().SqlRecoveryPoint = 1 diff --git a/contract/vm_dummy/vm_dummy_dbg.go b/contract/vm_dummy/vm_dummy_dbg.go deleted file mode 100644 index 52892417d..000000000 --- a/contract/vm_dummy/vm_dummy_dbg.go +++ /dev/null @@ -1,36 +0,0 @@ -//go:build Debug -// +build Debug - -package vm_dummy - -import ( - "math/big" - - luacUtil "github.com/aergoio/aergo/v2/cmd/aergoluac/util" - "github.com/aergoio/aergo/v2/contract" -) - -func getCompiledABI(code string) ([]byte, error) { - byteCodeAbi, err := contract.Compile(code, nil) - if err != nil { - return nil, err - } - return byteCodeAbi.ABI(), nil -} - -func NewLuaTxDeployBig(sender, recipient string, amount *big.Int, code string) *luaTxDeploy { - abi, err := getCompiledABI(code) - if err != nil { - return &luaTxDeploy{cErr: err} - } - return &luaTxDeploy{ - luaTxContractCommon: luaTxContractCommon{ - _sender: contract.StrHash(sender), - _recipient: contract.StrHash(recipient), - _payload: luacUtil.NewLuaCodePayload(luacUtil.NewLuaCode([]byte(code), abi), nil), - _amount: amount, - txId: newTxId(), - }, - cErr: nil, - } -} diff --git a/contract/vm_dummy/vm_dummy_pub_test.go b/contract/vm_dummy/vm_dummy_pub_test.go index 544d60c0b..69416ef87 100644 --- a/contract/vm_dummy/vm_dummy_pub_test.go +++ b/contract/vm_dummy/vm_dummy_pub_test.go @@ -365,26 +365,19 @@ func TestGasPerFunction(t *testing.T) { {"function_header_ops", "", 0, 143016}, {"assert", "", 0, 143146}, - {"getfenv", "", 0, 143041}, - {"metatable", "", 0, 143988}, {"ipairs", "", 0, 143039}, {"pairs", "", 0, 143039}, {"next", "", 0, 143087}, - {"rawequal", "", 0, 143216}, - {"rawget", "", 0, 143087}, - {"rawset", "", 0, 143941}, {"select", "", 0, 143166}, - {"setfenv", "", 0, 143076}, {"tonumber", "", 0, 143186}, {"tostring", "", 0, 143457}, {"type", "", 0, 143285}, {"unpack", "", 0, 150745}, - {"pcall", "", 0, 146165}, - {"xpcall", "", 0, 146437}, + {"pcall", "", 0, 147905}, + {"xpcall", "", 0, 148177}, {"string.byte", "", 0, 157040}, {"string.char", "", 0, 160397}, - {"string.dump", "", 0, 150349}, {"string.find", "", 0, 147808}, {"string.format", "", 0, 143764}, {"string.gmatch", "", 0, 143799}, @@ -452,7 +445,7 @@ func TestGasPerFunction(t *testing.T) { {"system.getSender", "", 0, 144261}, {"system.getBlockheight", "", 0, 143330}, - {"system.getTxhash", "", 0, 143737}, + //{"system.getTxhash", "", 0, 143734}, {"system.getTimestamp", "", 0, 143330}, {"system.getContractID", "", 0, 144261}, {"system.setItem", "", 0, 144194}, @@ -620,7 +613,7 @@ func TestGasOp(t *testing.T) { err = expectGas(string(code), 0, `"main"`, ``, 117610, SetHardForkVersion(3)) assert.NoError(t, err) - err = expectGas(string(code), 0, `"main"`, ``, 130048, SetHardForkVersion(4)) + err = expectGas(string(code), 0, `"main"`, ``, 120832, SetHardForkVersion(4)) assert.NoError(t, err) } @@ -628,18 +621,19 @@ func TestGasBF(t *testing.T) { skipNotOnAmd64(t) var err error - code := readLuaCode(t, "gas_bf.lua") + code2 := readLuaCode(t, "gas_bf_v2.lua") + code4 := readLuaCode(t, "gas_bf_v4.lua") - // err = expectGas(t, string(code), 0, `"main"`, ``, 100000, SetHardForkVersion(1)) + // err = expectGas(t, string(code), 0, `"main"`, ``, 100000, SetHardForkVersion(1), SetTimeout(500)) // assert.NoError(t, err) - err = expectGas(string(code), 0, `"main"`, ``, 47456244, SetHardForkVersion(2)) + err = expectGas(string(code2), 0, `"main"`, ``, 47456244, SetHardForkVersion(2), SetTimeout(500)) assert.NoError(t, err) - err = expectGas(string(code), 0, `"main"`, ``, 47456046, SetHardForkVersion(3)) + err = expectGas(string(code2), 0, `"main"`, ``, 47456046, SetHardForkVersion(3), SetTimeout(500)) assert.NoError(t, err) - err = expectGas(string(code), 0, `"main"`, ``, 57105265, SetHardForkVersion(4)) + err = expectGas(string(code4), 0, `"main"`, ``, 47342481, SetHardForkVersion(4), SetTimeout(500)) assert.NoError(t, err) } diff --git a/contract/vm_dummy/vm_dummy_release.go b/contract/vm_dummy/vm_dummy_release.go deleted file mode 100644 index f0a89f954..000000000 --- a/contract/vm_dummy/vm_dummy_release.go +++ /dev/null @@ -1,28 +0,0 @@ -//go:build !Debug -// +build !Debug - -package vm_dummy - -import ( - "math/big" - - "github.com/aergoio/aergo/v2/cmd/aergoluac/util" - "github.com/aergoio/aergo/v2/contract" -) - -func NewLuaTxDeployBig(sender, recipient string, amount *big.Int, code string) *luaTxDeploy { - byteCode, err := contract.Compile(code, nil) - if err != nil { - return &luaTxDeploy{cErr: err} - } - return &luaTxDeploy{ - luaTxContractCommon: luaTxContractCommon{ - _sender: contract.StrHash(sender), - _recipient: contract.StrHash(recipient), - _payload: util.NewLuaCodePayload(byteCode, nil), - _amount: amount, - txId: newTxId(), - }, - cErr: nil, - } -} diff --git a/contract/vm_dummy/vm_dummy_test.go b/contract/vm_dummy/vm_dummy_test.go index 604fb7bd6..c74a30ab8 100644 --- a/contract/vm_dummy/vm_dummy_test.go +++ b/contract/vm_dummy/vm_dummy_test.go @@ -21,9 +21,29 @@ import ( const min_version int32 = 2 const max_version int32 = 4 - const min_version_multicall int32 = 4 +func TestDisabledFunctions(t *testing.T) { + code := readLuaCode(t, "disabled-functions.lua") + + for version := int32(4); version <= max_version; version++ { + bc, err := LoadDummyChain(SetHardForkVersion(version), SetPubNet()) + require.NoErrorf(t, err, "failed to create dummy chain") + defer bc.Release() + + err = bc.ConnectBlock( + NewLuaTxAccount("user", 1, types.Aergo), + NewLuaTxDeploy("user", "test", 0, code), + ) + assert.NoErrorf(t, err, "failed to deploy contract") + + err = bc.ConnectBlock( + NewLuaTxCall("user", "test", 0, `{"Name":"check_disabled_functions","Args":[]}`), + ) + assert.NoErrorf(t, err, "failed execution") + } +} + func TestMaxCallDepth(t *testing.T) { //code := readLuaCode(t, "maxcalldepth_1.lua") // this contract receives a list of contract IDs to be called @@ -395,6 +415,66 @@ func TestContractSystem(t *testing.T) { exRv := fmt.Sprintf(`["%s","6FbDRScGruVdATaNWzD51xJkTfYCVwxSZDb7gzqCLzwf","AmhNNBNY7XFk4p5ym4CJf8nTcRTEHjWzAeXJfhP71244CjBCAQU3",%d,3,999]`, StrToAddress("user1"), bc.cBlock.Header.Timestamp/1e9) assert.Equal(t, exRv, receipt.GetRet(), "receipt ret error") + if version >= 4 { + + // system.version() + + tx = NewLuaTxCall("user1", "system", 0, `{"Name":"get_version", "Args":[]}`) + err = bc.ConnectBlock(tx) + require.NoErrorf(t, err, "failed to call tx") + + receipt = bc.GetReceipt(tx.Hash()) + expected := fmt.Sprintf(`%d`, version) + assert.Equal(t, expected, receipt.GetRet(), "receipt ret error") + + err = bc.Query("system", `{"Name":"get_version", "Args":[]}`, "", expected) + require.NoErrorf(t, err, "failed to query") + + // system.toPubKey() + + err = bc.Query("system", `{"Name":"to_pubkey", "Args":["AmgKtCaGjH4XkXwny2Jb1YH5gdsJGJh78ibWEgLmRWBS5LMfQuTf"]}`, "", `"0x0c3270bb25fea5bf0029b57e78581647a143265810b84940dd24e543ddc618ab91"`) + require.NoErrorf(t, err, "failed to query") + + err = bc.Query("system", `{"Name":"to_pubkey", "Args":["Amhmj6kKZz7mPstBAPJWRe1e8RHP7bZ5pV35XatqTHMWeAVSyMkc"]}`, "", `"0x0cf0d0fd04f44db75d66409346102167d67c40a5d76d46748fc4533f0265d0f83f"`) + require.NoErrorf(t, err, "failed to query") + + err = bc.Query("system", `{"Name":"to_pubkey", "Args":["6FbDRScGruVdATaNWzD51xJkTfYCVwxSZDb7gzqCLzwf"]}`, "invalid address length", "") + require.NoErrorf(t, err, "failed to query") + + err = bc.Query("system", `{"Name":"to_pubkey", "Args":["0x0c3270bb25fea5bf0029b57e78581647a143265810b84940dd24e543ddc618ab91"]}`, "invalid address length", "") + require.NoErrorf(t, err, "failed to query") + + err = bc.Query("system", `{"Name":"to_pubkey", "Args":[""]}`, "invalid address length", "") + require.NoErrorf(t, err, "failed to query") + + err = bc.Query("system", `{"Name":"to_pubkey", "Args":[]}`, "string expected, got nil", "") + require.NoErrorf(t, err, "failed to query") + + // system.toAddress() + + err = bc.Query("system", `{"Name":"to_address", "Args":["0x0c3270bb25fea5bf0029b57e78581647a143265810b84940dd24e543ddc618ab91"]}`, "", `"AmgKtCaGjH4XkXwny2Jb1YH5gdsJGJh78ibWEgLmRWBS5LMfQuTf"`) + require.NoErrorf(t, err, "failed to query") + + err = bc.Query("system", `{"Name":"to_address", "Args":["0x0cf0d0fd04f44db75d66409346102167d67c40a5d76d46748fc4533f0265d0f83f"]}`, "", `"Amhmj6kKZz7mPstBAPJWRe1e8RHP7bZ5pV35XatqTHMWeAVSyMkc"`) + require.NoErrorf(t, err, "failed to query") + + err = bc.Query("system", `{"Name":"to_address", "Args":["0cf0d0fd04f44db75d66409346102167d67c40a5d76d46748fc4533f0265d0f83f"]}`, "", `"Amhmj6kKZz7mPstBAPJWRe1e8RHP7bZ5pV35XatqTHMWeAVSyMkc"`) + require.NoErrorf(t, err, "failed to query") + + err = bc.Query("system", `{"Name":"to_address", "Args":["AmhNNBNY7XFk4p5ym4CJf8nTcRTEHjWzAeXJfhP71244CjBCAQU3"]}`, "invalid public key", "") + require.NoErrorf(t, err, "failed to query") + + err = bc.Query("system", `{"Name":"to_address", "Args":["6FbDRScGruVdATaNWzD51xJkTfYCVwxSZDb7gzqCLzwf"]}`, "invalid public key", "") + require.NoErrorf(t, err, "failed to query") + + err = bc.Query("system", `{"Name":"to_address", "Args":[""]}`, "invalid public key", "") + require.NoErrorf(t, err, "failed to query") + + err = bc.Query("system", `{"Name":"to_address", "Args":[]}`, "string expected, got nil", "") + require.NoErrorf(t, err, "failed to query") + + } + } } @@ -1375,17 +1455,47 @@ func TestSqlOnConflict(t *testing.T) { err = bc.ConnectBlock( NewLuaTxCall("user1", "on_conflict", 0, `{"name":"stmt_exec", "args": ["insert into t values (5)"]}`), - NewLuaTxCall("user1", "on_conflict", 0, `{"name":"stmt_exec", "args": ["insert or rollback into t values (5)"]}`).Fail("syntax error"), + NewLuaTxCall("user1", "on_conflict", 0, `{"name":"stmt_exec", "args": ["insert or rollback into t values (6),(5),(7)"]}`).Fail("syntax error"), ) require.NoErrorf(t, err, "failed to call tx") err = bc.Query("on_conflict", `{"name":"get"}`, "", `[1,2,3,4,5]`) require.NoErrorf(t, err, "failed to query") - err = bc.ConnectBlock(NewLuaTxCall("user1", "on_conflict", 0, `{"name":"stmt_exec_pcall", "args": ["insert or fail into t values (6),(7),(5),(8),(9)"]}`)) + err = bc.ConnectBlock(NewLuaTxCall("user1", "on_conflict", 0, `{"name":"stmt_exec", "args": ["insert or abort into t values (6),(7),(5),(8),(9)"]}`).Fail("UNIQUE constraint failed")) + require.NoErrorf(t, err, "failed to call tx") + + err = bc.Query("on_conflict", `{"name":"get"}`, "", `[1,2,3,4,5]`) + require.NoErrorf(t, err, "failed to query") + + // successful pcall + err = bc.ConnectBlock(NewLuaTxCall("user1", "on_conflict", 0, `{"name":"stmt_exec_pcall", "args": ["insert into t values (6)"]}`)) + require.NoErrorf(t, err, "failed to call tx") + + err = bc.Query("on_conflict", `{"name":"get"}`, "", `[1,2,3,4,5,6]`) + require.NoErrorf(t, err, "failed to query") + + // pcall fails but the tx succeeds + err = bc.ConnectBlock(NewLuaTxCall("user1", "on_conflict", 0, `{"name":"stmt_exec_pcall", "args": ["insert or fail into t values (7),(5),(8)"]}`)) + require.NoErrorf(t, err, "failed to call tx") + + var expected string + if version >= 4 { + // pcall reverts the changes + expected = `[1,2,3,4,5,6]` + } else { + // pcall does not revert the changes + expected = `[1,2,3,4,5,6,7]` + } + + err = bc.Query("on_conflict", `{"name":"get"}`, "", expected) + require.NoErrorf(t, err, "failed to query") + + // here the tx is reverted + err = bc.ConnectBlock(NewLuaTxCall("user1", "on_conflict", 0, `{"name":"stmt_exec", "args": ["insert or fail into t values (7),(5),(8)"]}`).Fail("UNIQUE constraint failed")) require.NoErrorf(t, err, "failed to call tx") - err = bc.Query("on_conflict", `{"name":"get"}`, "", `[1,2,3,4,5,6,7]`) + err = bc.Query("on_conflict", `{"name":"get"}`, "", expected) require.NoErrorf(t, err, "failed to query") } @@ -2391,6 +2501,132 @@ func TestTypeBignum(t *testing.T) { } } +func TestBignumValues(t *testing.T) { + code := readLuaCode(t, "bignum_values.lua") + + bc, err := LoadDummyChain(SetHardForkVersion(2)) + require.NoErrorf(t, err, "failed to create dummy chain") + defer bc.Release() + + err = bc.ConnectBlock( + NewLuaTxAccount("user1", 1, types.Aergo), + NewLuaTxDeploy("user1", "contract1", 0, code), + ) + require.NoErrorf(t, err, "failed to deploy") + + // hardfork 2 + + // process octal, hex, binary + + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":["0"]}`, "", `"0"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":["9"]}`, "", `"9"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":["0055"]}`, "", `"45"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":["01234567"]}`, "", `"342391"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":["0x123456789abcdef"]}`, "", `"81985529216486895"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":["0b1010101010101"]}`, "", `"5461"`) + require.NoErrorf(t, err, "failed to query") + + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":[{"_bignum":"0"}]}`, "", `"0"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":[{"_bignum":"9"}]}`, "", `"9"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":[{"_bignum":"01234567"}]}`, "", `"342391"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":[{"_bignum":"0x123456789abcdef"}]}`, "", `"81985529216486895"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":[{"_bignum":"0b1010101010101"}]}`, "", `"5461"`) + require.NoErrorf(t, err, "failed to query") + + + // hardfork 3 + bc.HardforkVersion = 3 + + // block octal, hex and binary + + tx := NewLuaTxCall("user1", "contract1", 0, `{"Name":"parse_bignum", "Args":["01234567"]}`) + err = bc.ConnectBlock(tx) + require.NoErrorf(t, err, "failed to call tx") + receipt := bc.GetReceipt(tx.Hash()) + assert.Equalf(t, `"1234567"`, receipt.GetRet(), "contract Call ret error") + + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":["0"]}`, "", `"0"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":["9"]}`, "", `"9"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":["0055"]}`, "", `"55"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":["01234567"]}`, "", `"1234567"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":["0x123456789abcdef"]}`, "bignum invalid number string", `""`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":["0b1010101010101"]}`, "bignum invalid number string", `""`) + require.NoErrorf(t, err, "failed to query") + + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":[{"_bignum":"0"}]}`, "", `"0"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":[{"_bignum":"9"}]}`, "", `"9"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":[{"_bignum":"01234567"}]}`, "", `"1234567"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":[{"_bignum":"0x123456789abcdef"}]}`, "bignum invalid number string", `""`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":[{"_bignum":"0b1010101010101"}]}`, "bignum invalid number string", `""`) + require.NoErrorf(t, err, "failed to query") + + + // hardfork 4 and after + + for version := int32(4); version <= max_version; version++ { + bc, err = LoadDummyChain(SetHardForkVersion(version)) + require.NoErrorf(t, err, "failed to create dummy chain") + defer bc.Release() + + err = bc.ConnectBlock( + NewLuaTxAccount("user1", 1, types.Aergo), + NewLuaTxDeploy("user1", "contract1", 0, code), + ) + require.NoErrorf(t, err, "failed to deploy") + + // process hex, binary. block octal + + tx = NewLuaTxCall("user1", "contract1", 0, `{"Name":"parse_bignum", "Args":["01234567"]}`) + err = bc.ConnectBlock(tx) + require.NoErrorf(t, err, "failed to call tx") + receipt = bc.GetReceipt(tx.Hash()) + assert.Equalf(t, `"1234567"`, receipt.GetRet(), "contract Call ret error") + + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":["0"]}`, "", `"0"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":["9"]}`, "", `"9"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":["0055"]}`, "", `"55"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":["01234567"]}`, "", `"1234567"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":["0x123456789abcdef"]}`, "", `"81985529216486895"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":["0b1010101010101"]}`, "", `"5461"`) + require.NoErrorf(t, err, "failed to query") + + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":[{"_bignum":"0"}]}`, "", `"0"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":[{"_bignum":"9"}]}`, "", `"9"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":[{"_bignum":"01234567"}]}`, "", `"1234567"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":[{"_bignum":"0x123456789abcdef"}]}`, "", `"81985529216486895"`) + require.NoErrorf(t, err, "failed to query") + err = bc.Query("contract1", `{"Name":"parse_bignum", "Args":[{"_bignum":"0b1010101010101"}]}`, "", `"5461"`) + require.NoErrorf(t, err, "failed to query") + + } +} + func checkRandomIntValue(v string, min, max int) error { n, _ := strconv.Atoi(v) if n < min || n > max { @@ -2773,964 +3009,989 @@ func TestFeaturePcallNested(t *testing.T) { // test rollback of state variable and balance func TestPcallStateRollback1(t *testing.T) { - code := readLuaCode(t, "feature_pcall_rollback_4.lua") resolver := readLuaCode(t, "resolver.lua") for version := min_version; version <= max_version; version++ { - bc, err := LoadDummyChain(SetHardForkVersion(version)) - require.NoErrorf(t, err, "failed to create dummy chain") - defer bc.Release() - // deploy and setup the name resolver - err = bc.ConnectBlock( - NewLuaTxAccount("user", 10, types.Aergo), - NewLuaTxDeploy("user", "resolver", 0, resolver), - NewLuaTxCall("user", "resolver", 0, fmt.Sprintf(`{"Name":"set","Args":["A","%s"]}`, nameToAddress("A"))), - NewLuaTxCall("user", "resolver", 0, fmt.Sprintf(`{"Name":"set","Args":["B","%s"]}`, nameToAddress("B"))), - NewLuaTxCall("user", "resolver", 0, fmt.Sprintf(`{"Name":"set","Args":["C","%s"]}`, nameToAddress("C"))), - ) - require.NoErrorf(t, err, "failed to deploy and setup resolver") + files := make([]string, 0) + files = append(files, "feature_pcall_rollback_4a.lua") // contract.pcall + if version >= 4 { + files = append(files, "feature_pcall_rollback_4b.lua") // pcall + files = append(files, "feature_pcall_rollback_4c.lua") // xpcall + } - // deploy the contracts - err = bc.ConnectBlock( - NewLuaTxDeploy("user", "A", 3, code).Constructor(fmt.Sprintf(`["%s","A"]`, nameToAddress("resolver"))), - NewLuaTxDeploy("user", "B", 0, code).Constructor(fmt.Sprintf(`["%s","B"]`, nameToAddress("resolver"))), - NewLuaTxDeploy("user", "C", 0, code).Constructor(fmt.Sprintf(`["%s","C"]`, nameToAddress("resolver"))), - ) - require.NoErrorf(t, err, "failed to deploy the contracts") - - // A -> A -> A (3 calls on the same contract) - - script := `[[ - ['set','x',111], - ['pcall','A'] - ],[ - ['set','x',222], - ['pcall','A'] - ],[ - ['set','x',333] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 333}, nil) - - script = `[[ - ['set','x',111], - ['pcall','A'] - ],[ - ['set','x',222], - ['pcall','A'] - ],[ - ['set','x',333], - ['fail'] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 222}, nil) - - script = `[[ - ['set','x',111], - ['pcall','A'] - ],[ - ['set','x',222], - ['pcall','A'], - ['fail'] - ],[ - ['set','x',333] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111}, nil) - - script = `[[ - ['set','x',111], - ['pcall','A'], - ['fail'] - ],[ - ['set','x',222], - ['pcall','A'] - ],[ - ['set','x',333] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 0}, nil) - - // A -> B -> C (3 different contracts) - - script = `[[ - ['set','x',111], - ['pcall','B',2] - ],[ - ['set','x',222], - ['pcall','C',1] - ],[ - ['set','x',333] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 222, "C": 333}, - map[string]int64{"A": 1, "B": 1, "C": 1}) - - script = `[[ - ['set','x',111], - ['pcall','B',2] - ],[ - ['set','x',222], - ['pcall','C',1] - ],[ - ['set','x',333], - ['fail'] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 222, "C": 0}, - map[string]int64{"A": 1, "B": 2, "C": 0}) - - script = `[[ - ['set','x',111], - ['pcall','B',2] - ],[ - ['set','x',222], - ['pcall','C',1], - ['fail'] - ],[ - ['set','x',333] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 0, "C": 0}, - map[string]int64{"A": 3, "B": 0, "C": 0}) - - script = `[[ - ['set','x',111], - ['pcall','B',2], - ['fail'] - ],[ - ['set','x',222], - ['pcall','C',1] - ],[ - ['set','x',333] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 0, "B": 0, "C": 0}, - map[string]int64{"A": 3, "B": 0, "C": 0}) - - // A -> B -> A (call back to original contract) - - script = `[[ - ['set','x',111], - ['pcall','B',2] - ],[ - ['set','x',222], - ['pcall','A',1] - ],[ - ['set','x',333] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 333, "B": 222}, - map[string]int64{"A": 2, "B": 1}) - - script = `[[ - ['set','x',111], - ['pcall','B',2] - ],[ - ['set','x',222], - ['pcall','A',1] - ],[ - ['set','x',333], - ['fail'] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 222}, - map[string]int64{"A": 1, "B": 2}) - - script = `[[ - ['set','x',111], - ['pcall','B',2] - ],[ - ['set','x',222], - ['pcall','A',1], - ['fail'] - ],[ - ['set','x',333] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 0}, - map[string]int64{"A": 3, "B": 0}) - - script = `[[ - ['set','x',111], - ['pcall','B',2], - ['fail'] - ],[ - ['set','x',222], - ['pcall','A',1] - ],[ - ['set','x',333] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 0, "B": 0}, - map[string]int64{"A": 3, "B": 0}) - - // A -> B -> B - - script = `[[ - ['set','x',111], - ['pcall','B',3] - ],[ - ['set','x',222], - ['pcall','B'] - ],[ - ['set','x',333] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 333}, - map[string]int64{"A": 0, "B": 3}) - - script = `[[ - ['set','x',111], - ['pcall','B',3] - ],[ - ['set','x',222], - ['pcall','B'] - ],[ - ['set','x',333], - ['fail'] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 222}, - map[string]int64{"A": 0, "B": 3}) - - script = `[[ - ['set','x',111], - ['pcall','B',3] - ],[ - ['set','x',222], - ['pcall','B'], - ['fail'] - ],[ - ['set','x',333] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 0}, - map[string]int64{"A": 3, "B": 0}) - - script = `[[ - ['set','x',111], - ['pcall','B',3], - ['fail'] - ],[ - ['set','x',222], - ['pcall','B'] - ],[ - ['set','x',333] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 0, "B": 0}, - map[string]int64{"A": 3, "B": 0}) - - // A -> A -> B - - script = `[[ - ['set','x',111], - ['pcall','A'] - ],[ - ['set','x',222], - ['pcall','B',3] - ],[ - ['set','x',333] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 222, "B": 333}, - map[string]int64{"A": 0, "B": 3}) - - script = `[[ - ['set','x',111], - ['pcall','A'] - ],[ - ['set','x',222], - ['pcall','B',3] - ],[ - ['set','x',333], - ['fail'] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 222, "B": 0}, - map[string]int64{"A": 3, "B": 0}) - - script = `[[ - ['set','x',111], - ['pcall','A'] - ],[ - ['set','x',222], - ['pcall','B',3], - ['fail'] - ],[ - ['set','x',333] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 0}, - map[string]int64{"A": 3, "B": 0}) - - script = `[[ - ['set','x',111], - ['pcall','A'], - ['fail'] - ],[ - ['set','x',222], - ['pcall','B',3] - ],[ - ['set','x',333] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 0, "B": 0}, - map[string]int64{"A": 3, "B": 0}) - - // A -> B -> A -> B -> A (zigzag) - - script = `[[ - ['set','x',111], - ['pcall','B',1] - ],[ - ['set','x',222], - ['pcall','A',1] - ],[ - ['set','x',333], - ['pcall','B',1] - ],[ - ['set','x',444], - ['pcall','A',1] - ],[ - ['set','x',555] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 555, "B": 444}, - map[string]int64{"A": 3, "B": 0}) - - script = `[[ - ['set','x',111], - ['pcall','B',1] - ],[ - ['set','x',222], - ['pcall','A',1] - ],[ - ['set','x',333], - ['pcall','B',1] - ],[ - ['set','x',444], - ['pcall','A',1] - ],[ - ['set','x',555], - ['fail'] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 333, "B": 444}, - map[string]int64{"A": 2, "B": 1}) - - script = `[[ - ['set','x',111], - ['pcall','B',1] - ],[ - ['set','x',222], - ['pcall','A',1] - ],[ - ['set','x',333], - ['pcall','B',1] - ],[ - ['set','x',444], - ['pcall','A',1], - ['fail'] - ],[ - ['set','x',555] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 333, "B": 222}, - map[string]int64{"A": 3, "B": 0}) - - script = `[[ - ['set','x',111], - ['pcall','B',1] - ],[ - ['set','x',222], - ['pcall','A',1] - ],[ - ['set','x',333], - ['pcall','B',1], - ['fail'] - ],[ - ['set','x',444], - ['pcall','A',1] - ],[ - ['set','x',555] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 222}, - map[string]int64{"A": 2, "B": 1}) - - script = `[[ - ['set','x',111], - ['pcall','B',1] - ],[ - ['set','x',222], - ['pcall','A',1], - ['fail'] - ],[ - ['set','x',333], - ['pcall','B',1] - ],[ - ['set','x',444], - ['pcall','A',1] - ],[ - ['set','x',555] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 0}, - map[string]int64{"A": 3, "B": 0}) - - script = `[[ - ['set','x',111], - ['pcall','B',1], - ['fail'] - ],[ - ['set','x',222], - ['pcall','A',1] - ],[ - ['set','x',333], - ['pcall','B',1] - ],[ - ['set','x',444], - ['pcall','A',1] - ],[ - ['set','x',555] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 0, "B": 0}, - map[string]int64{"A": 3, "B": 0}) + // iterate over all files + for _, file := range files { + + code := readLuaCode(t, file) + + bc, err := LoadDummyChain(SetHardForkVersion(version)) + require.NoErrorf(t, err, "failed to create dummy chain") + defer bc.Release() + + // deploy and setup the name resolver + err = bc.ConnectBlock( + NewLuaTxAccount("user", 10, types.Aergo), + NewLuaTxDeploy("user", "resolver", 0, resolver), + NewLuaTxCall("user", "resolver", 0, fmt.Sprintf(`{"Name":"set","Args":["A","%s"]}`, nameToAddress("A"))), + NewLuaTxCall("user", "resolver", 0, fmt.Sprintf(`{"Name":"set","Args":["B","%s"]}`, nameToAddress("B"))), + NewLuaTxCall("user", "resolver", 0, fmt.Sprintf(`{"Name":"set","Args":["C","%s"]}`, nameToAddress("C"))), + ) + require.NoErrorf(t, err, "failed to deploy and setup resolver") + + // deploy the contracts + err = bc.ConnectBlock( + NewLuaTxDeploy("user", "A", 3, code).Constructor(fmt.Sprintf(`["%s","A"]`, nameToAddress("resolver"))), + NewLuaTxDeploy("user", "B", 0, code).Constructor(fmt.Sprintf(`["%s","B"]`, nameToAddress("resolver"))), + NewLuaTxDeploy("user", "C", 0, code).Constructor(fmt.Sprintf(`["%s","C"]`, nameToAddress("resolver"))), + ) + require.NoErrorf(t, err, "failed to deploy the contracts") + + // A -> A -> A (3 calls on the same contract) + + script := `[[ + ['set','x',111], + ['pcall','A'] + ],[ + ['set','x',222], + ['pcall','A'] + ],[ + ['set','x',333] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 333}, nil) + + script = `[[ + ['set','x',111], + ['pcall','A'] + ],[ + ['set','x',222], + ['pcall','A'] + ],[ + ['set','x',333], + ['fail'] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 222}, nil) + + script = `[[ + ['set','x',111], + ['pcall','A'] + ],[ + ['set','x',222], + ['pcall','A'], + ['fail'] + ],[ + ['set','x',333] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111}, nil) + + script = `[[ + ['set','x',111], + ['pcall','A'], + ['fail'] + ],[ + ['set','x',222], + ['pcall','A'] + ],[ + ['set','x',333] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 0}, nil) + + // A -> B -> C (3 different contracts) + + script = `[[ + ['set','x',111], + ['pcall','B',2] + ],[ + ['set','x',222], + ['pcall','C',1] + ],[ + ['set','x',333] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 222, "C": 333}, + map[string]int64{"A": 1, "B": 1, "C": 1}) + + script = `[[ + ['set','x',111], + ['pcall','B',2] + ],[ + ['set','x',222], + ['pcall','C',1] + ],[ + ['set','x',333], + ['fail'] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 222, "C": 0}, + map[string]int64{"A": 1, "B": 2, "C": 0}) + + script = `[[ + ['set','x',111], + ['pcall','B',2] + ],[ + ['set','x',222], + ['pcall','C',1], + ['fail'] + ],[ + ['set','x',333] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 0, "C": 0}, + map[string]int64{"A": 3, "B": 0, "C": 0}) + + script = `[[ + ['set','x',111], + ['pcall','B',2], + ['fail'] + ],[ + ['set','x',222], + ['pcall','C',1] + ],[ + ['set','x',333] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 0, "B": 0, "C": 0}, + map[string]int64{"A": 3, "B": 0, "C": 0}) + + // A -> B -> A (call back to original contract) + + script = `[[ + ['set','x',111], + ['pcall','B',2] + ],[ + ['set','x',222], + ['pcall','A',1] + ],[ + ['set','x',333] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 333, "B": 222}, + map[string]int64{"A": 2, "B": 1}) + + script = `[[ + ['set','x',111], + ['pcall','B',2] + ],[ + ['set','x',222], + ['pcall','A',1] + ],[ + ['set','x',333], + ['fail'] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 222}, + map[string]int64{"A": 1, "B": 2}) + + script = `[[ + ['set','x',111], + ['pcall','B',2] + ],[ + ['set','x',222], + ['pcall','A',1], + ['fail'] + ],[ + ['set','x',333] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 0}, + map[string]int64{"A": 3, "B": 0}) + + script = `[[ + ['set','x',111], + ['pcall','B',2], + ['fail'] + ],[ + ['set','x',222], + ['pcall','A',1] + ],[ + ['set','x',333] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 0, "B": 0}, + map[string]int64{"A": 3, "B": 0}) + + // A -> B -> B + + script = `[[ + ['set','x',111], + ['pcall','B',3] + ],[ + ['set','x',222], + ['pcall','B'] + ],[ + ['set','x',333] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 333}, + map[string]int64{"A": 0, "B": 3}) + + script = `[[ + ['set','x',111], + ['pcall','B',3] + ],[ + ['set','x',222], + ['pcall','B'] + ],[ + ['set','x',333], + ['fail'] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 222}, + map[string]int64{"A": 0, "B": 3}) + + script = `[[ + ['set','x',111], + ['pcall','B',3] + ],[ + ['set','x',222], + ['pcall','B'], + ['fail'] + ],[ + ['set','x',333] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 0}, + map[string]int64{"A": 3, "B": 0}) + + script = `[[ + ['set','x',111], + ['pcall','B',3], + ['fail'] + ],[ + ['set','x',222], + ['pcall','B'] + ],[ + ['set','x',333] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 0, "B": 0}, + map[string]int64{"A": 3, "B": 0}) + + // A -> A -> B + + script = `[[ + ['set','x',111], + ['pcall','A'] + ],[ + ['set','x',222], + ['pcall','B',3] + ],[ + ['set','x',333] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 222, "B": 333}, + map[string]int64{"A": 0, "B": 3}) + + script = `[[ + ['set','x',111], + ['pcall','A'] + ],[ + ['set','x',222], + ['pcall','B',3] + ],[ + ['set','x',333], + ['fail'] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 222, "B": 0}, + map[string]int64{"A": 3, "B": 0}) + + script = `[[ + ['set','x',111], + ['pcall','A'] + ],[ + ['set','x',222], + ['pcall','B',3], + ['fail'] + ],[ + ['set','x',333] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 0}, + map[string]int64{"A": 3, "B": 0}) + + script = `[[ + ['set','x',111], + ['pcall','A'], + ['fail'] + ],[ + ['set','x',222], + ['pcall','B',3] + ],[ + ['set','x',333] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 0, "B": 0}, + map[string]int64{"A": 3, "B": 0}) + + // A -> B -> A -> B -> A (zigzag) + + script = `[[ + ['set','x',111], + ['pcall','B',1] + ],[ + ['set','x',222], + ['pcall','A',1] + ],[ + ['set','x',333], + ['pcall','B',1] + ],[ + ['set','x',444], + ['pcall','A',1] + ],[ + ['set','x',555] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 555, "B": 444}, + map[string]int64{"A": 3, "B": 0}) + + script = `[[ + ['set','x',111], + ['pcall','B',1] + ],[ + ['set','x',222], + ['pcall','A',1] + ],[ + ['set','x',333], + ['pcall','B',1] + ],[ + ['set','x',444], + ['pcall','A',1] + ],[ + ['set','x',555], + ['fail'] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 333, "B": 444}, + map[string]int64{"A": 2, "B": 1}) + + script = `[[ + ['set','x',111], + ['pcall','B',1] + ],[ + ['set','x',222], + ['pcall','A',1] + ],[ + ['set','x',333], + ['pcall','B',1] + ],[ + ['set','x',444], + ['pcall','A',1], + ['fail'] + ],[ + ['set','x',555] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 333, "B": 222}, + map[string]int64{"A": 3, "B": 0}) + + script = `[[ + ['set','x',111], + ['pcall','B',1] + ],[ + ['set','x',222], + ['pcall','A',1] + ],[ + ['set','x',333], + ['pcall','B',1], + ['fail'] + ],[ + ['set','x',444], + ['pcall','A',1] + ],[ + ['set','x',555] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 222}, + map[string]int64{"A": 2, "B": 1}) + + script = `[[ + ['set','x',111], + ['pcall','B',1] + ],[ + ['set','x',222], + ['pcall','A',1], + ['fail'] + ],[ + ['set','x',333], + ['pcall','B',1] + ],[ + ['set','x',444], + ['pcall','A',1] + ],[ + ['set','x',555] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 0}, + map[string]int64{"A": 3, "B": 0}) + + script = `[[ + ['set','x',111], + ['pcall','B',1], + ['fail'] + ],[ + ['set','x',222], + ['pcall','A',1] + ],[ + ['set','x',333], + ['pcall','B',1] + ],[ + ['set','x',444], + ['pcall','A',1] + ],[ + ['set','x',555] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 0, "B": 0}, + map[string]int64{"A": 3, "B": 0}) + } } } // test rollback of state variable and balance - send separate from call func TestPcallStateRollback2(t *testing.T) { t.Skip("disabled until bug with test is fixed") - code := readLuaCode(t, "feature_pcall_rollback_4.lua") resolver := readLuaCode(t, "resolver.lua") for version := min_version; version <= max_version; version++ { - bc, err := LoadDummyChain(SetHardForkVersion(version)) - require.NoErrorf(t, err, "failed to create dummy chain") - defer bc.Release() + files := make([]string, 0) + files = append(files, "feature_pcall_rollback_4a.lua") // contract.pcall + if version >= 4 { + files = append(files, "feature_pcall_rollback_4b.lua") // pcall + files = append(files, "feature_pcall_rollback_4c.lua") // xpcall + } - // deploy and setup the name resolver - err = bc.ConnectBlock( - NewLuaTxAccount("user", 10, types.Aergo), - NewLuaTxDeploy("user", "resolver", 0, resolver), - NewLuaTxCall("user", "resolver", 0, fmt.Sprintf(`{"Name":"set","Args":["A","%s"]}`, nameToAddress("A"))), - NewLuaTxCall("user", "resolver", 0, fmt.Sprintf(`{"Name":"set","Args":["B","%s"]}`, nameToAddress("B"))), - NewLuaTxCall("user", "resolver", 0, fmt.Sprintf(`{"Name":"set","Args":["C","%s"]}`, nameToAddress("C"))), - NewLuaTxCall("user", "resolver", 0, fmt.Sprintf(`{"Name":"set","Args":["D","%s"]}`, nameToAddress("D"))), - NewLuaTxCall("user", "resolver", 0, fmt.Sprintf(`{"Name":"set","Args":["E","%s"]}`, nameToAddress("E"))), - ) - require.NoErrorf(t, err, "failed to deploy and setup resolver") + // iterate over all files + for _, file := range files { - // deploy the contracts - err = bc.ConnectBlock( - NewLuaTxDeploy("user", "A", 3, code).Constructor(fmt.Sprintf(`["%s","A"]`, nameToAddress("resolver"))), - NewLuaTxDeploy("user", "B", 0, code).Constructor(fmt.Sprintf(`["%s","B"]`, nameToAddress("resolver"))), - NewLuaTxDeploy("user", "C", 0, code).Constructor(fmt.Sprintf(`["%s","C"]`, nameToAddress("resolver"))), - ) - require.NoErrorf(t, err, "failed to deploy the contracts") - - // A -> A -> A (3 calls on the same contract) - - script := `[[ - ['set','x',111], - ['send','B',1], - ['pcall','A'] - ],[ - ['set','x',222], - ['send','C',1], - ['pcall','A'] - ],[ - ['set','x',333], - ['send','E',1] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 333}, - map[string]int64{"A": 0, "B": 1, "C": 1, "E": 1}) - - script = `[[ - ['set','x',111], - ['send','B',1], - ['pcall','A'] - ],[ - ['set','x',222], - ['send','C',1], - ['pcall','A'] - ],[ - ['set','x',333], - ['send','D',1], - ['fail'] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 222}, - map[string]int64{"A": 1, "B": 1, "C": 1, "D": 0}) - - script = `[[ - ['set','x',111], - ['send','B',1], - ['pcall','A'] - ],[ - ['set','x',222], - ['send','C',1], - ['pcall','A'], - ['fail'] - ],[ - ['set','x',333], - ['send','D',1] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111}, - map[string]int64{"A": 2, "B": 1, "C": 0, "D": 0}) - - script = `[[ - ['set','x',111], - ['send','B',1], - ['pcall','A'], - ['fail'] - ],[ - ['set','x',222], - ['send','C',1], - ['pcall','A'] - ],[ - ['set','x',333], - ['send','D',1] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 0}, - map[string]int64{"A": 3, "B": 0, "C": 0, "D": 0}) - - // A -> B -> C (3 different contracts) - - script = `[[ - ['set','x',111], - ['send','B',3], - ['pcall','B'] - ],[ - ['set','x',222], - ['send','C',2], - ['pcall','C'] - ],[ - ['set','x',333], - ['send','A',1] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 222, "C": 333}, - map[string]int64{"A": 1, "B": 1, "C": 1}) - - script = `[[ - ['set','x',111], - ['send','B',3], - ['pcall','B'] - ],[ - ['set','x',222], - ['send','C',2], - ['pcall','C'] - ],[ - ['set','x',333], - ['send','A',1], - ['fail'] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 222, "C": 0}, - map[string]int64{"A": 0, "B": 1, "C": 2}) - - script = `[[ - ['set','x',111], - ['send','B',3], - ['pcall','B'] - ],[ - ['set','x',222], - ['send','C',2], - ['pcall','C'], - ['fail'] - ],[ - ['set','x',333], - ['send','A',1] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 0, "C": 0}, - map[string]int64{"A": 0, "B": 3, "C": 0}) - - script = `[[ - ['set','x',111], - ['send','B',3], - ['pcall','B'], - ['fail'] - ],[ - ['set','x',222], - ['send','C',2], - ['pcall','C'] - ],[ - ['set','x',333], - ['send','A',1] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 0, "B": 0, "C": 0}, - map[string]int64{"A": 3, "B": 0, "C": 0}) - - // A -> B -> A (call back to original contract) - - script = `[[ - ['set','x',111], - ['send','B',3], - ['pcall','B'] - ],[ - ['set','x',222], - ['send','A',2], - ['pcall','A'] - ],[ - ['set','x',333], - ['send','B',1] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 333, "B": 222}, - map[string]int64{"A": 1, "B": 2}) - - script = `[[ - ['set','x',111], - ['send','B',3], - ['pcall','B'] - ],[ - ['set','x',222], - ['send','A',2], - ['pcall','A'] - ],[ - ['set','x',333], - ['send','B',1], - ['fail'] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 222}, - map[string]int64{"A": 2, "B": 1}) - - script = `[[ - ['set','x',111], - ['send','B',3], - ['pcall','B'] - ],[ - ['set','x',222], - ['send','A',2], - ['pcall','A'], - ['fail'] - ],[ - ['set','x',333], - ['send','B',1] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 0}, - map[string]int64{"A": 0, "B": 3}) - - script = `[[ - ['set','x',111], - ['send','B',3], - ['pcall','B'], - ['fail'] - ],[ - ['set','x',222], - ['send','A',2], - ['pcall','A'] - ],[ - ['set','x',333], - ['send','B',1] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 0, "B": 0}, - map[string]int64{"A": 3, "B": 0}) - - // A -> B -> B - - script = `[[ - ['set','x',111], - ['send','B',3], - ['pcall','B'] - ],[ - ['set','x',222], - ['send','C',1], - ['pcall','B'] - ],[ - ['set','x',333], - ['send','A',1] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 333}, - map[string]int64{"A": 1, "B": 1, "C": 1}) - - script = `[[ - ['set','x',111], - ['send','B',3], - ['pcall','B'] - ],[ - ['set','x',222], - ['send','C',1], - ['pcall','B'] - ],[ - ['set','x',333], - ['send','A',1], - ['fail'] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 222}, - map[string]int64{"A": 0, "B": 2, "C": 1}) - - script = `[[ - ['set','x',111], - ['send','B',3], - ['pcall','B'] - ],[ - ['set','x',222], - ['send','C',1], - ['pcall','B'], - ['fail'] - ],[ - ['set','x',333], - ['send','A',1] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 0}, - map[string]int64{"A": 0, "B": 3, "C": 0}) - - script = `[[ - ['set','x',111], - ['send','B',3], - ['pcall','B'], - ['fail'] - ],[ - ['set','x',222], - ['send','C',1], - ['pcall','B'] - ],[ - ['set','x',333], - ['send','A',1] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 0, "B": 0}, - map[string]int64{"A": 3, "B": 0, "C": 0}) - - // A -> A -> B - - script = `[[ - ['set','x',111], - ['send','B',2], - ['pcall','A'] - ],[ - ['set','x',222], - ['send','C',1], - ['pcall','B'] - ],[ - ['set','x',333], - ['send','A',1] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 222, "B": 333}, - map[string]int64{"A": 1, "B": 1, "C": 1}) - - script = `[[ - ['set','x',111], - ['send','B',2], - ['pcall','A'] - ],[ - ['set','x',222], - ['send','C',1], - ['pcall','B'] - ],[ - ['set','x',333], - ['send','A',1], - ['fail'] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 222, "B": 0}, - map[string]int64{"A": 0, "B": 2, "C": 1}) - - script = `[[ - ['set','x',111], - ['send','B',2], - ['pcall','A'] - ],[ - ['set','x',222], - ['send','C',1], - ['pcall','B'], - ['fail'] - ],[ - ['set','x',333], - ['send','A',1] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 0}, - map[string]int64{"A": 1, "B": 2, "C": 0}) - - script = `[[ - ['set','x',111], - ['send','B',2], - ['pcall','A'], - ['fail'] - ],[ - ['set','x',222], - ['send','C',1], - ['pcall','B'] - ],[ - ['set','x',333], - ['send','A',1] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 0, "B": 0}, - map[string]int64{"A": 3, "B": 0, "C": 0}) - - // A -> B -> A -> B -> A (zigzag) - - script = `[[ - ['set','x',111], - ['send','B',1], - ['pcall','B'] - ],[ - ['set','x',222], - ['send','A',1], - ['pcall','A'] - ],[ - ['set','x',333], - ['send','B',1], - ['pcall','B'] - ],[ - ['set','x',444], - ['send','A',1], - ['pcall','A'] - ],[ - ['set','x',555], - ['send','B',1] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 555, "B": 444}, - map[string]int64{"A": 2, "B": 1}) - - script = `[[ - ['set','x',111], - ['send','B',1], - ['pcall','B'] - ],[ - ['set','x',222], - ['send','A',1], - ['pcall','A'] - ],[ - ['set','x',333], - ['send','B',1], - ['pcall','B'] - ],[ - ['set','x',444], - ['send','A',1], - ['pcall','A'] - ],[ - ['set','x',555], - ['send','B',1], - ['fail'] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 333, "B": 444}, - map[string]int64{"A": 3, "B": 0}) - - script = `[[ - ['set','x',111], - ['send','B',1], - ['pcall','B'] - ],[ - ['set','x',222], - ['send','A',1], - ['pcall','A'] - ],[ - ['set','x',333], - ['send','B',1], - ['pcall','B'] - ],[ - ['set','x',444], - ['send','A',1], - ['pcall','A'], - ['fail'] - ],[ - ['set','x',555], - ['send','B',1] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 333, "B": 222}, - map[string]int64{"A": 2, "B": 1}) - - script = `[[ - ['set','x',111], - ['send','B',1], - ['pcall','B'] - ],[ - ['set','x',222], - ['send','A',1], - ['pcall','A'] - ],[ - ['set','x',333], - ['send','B',1], - ['pcall','B'], - ['fail'] - ],[ - ['set','x',444], - ['send','A',1], - ['pcall','A'] - ],[ - ['set','x',555], - ['send','B',1] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 222}, - map[string]int64{"A": 3, "B": 0}) - - script = `[[ - ['set','x',111], - ['send','B',1], - ['pcall','B'] - ],[ - ['set','x',222], - ['send','A',1], - ['pcall','A'], - ['fail'] - ],[ - ['set','x',333], - ['send','B',1], - ['pcall','B'] - ],[ - ['set','x',444], - ['send','A',1], - ['pcall','A'] - ],[ - ['set','x',555], - ['send','B',1] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 0}, - map[string]int64{"A": 2, "B": 1}) - - script = `[[ - ['set','x',111], - ['send','B',1], - ['pcall','B'], - ['fail'] - ],[ - ['set','x',222], - ['send','A',1], - ['pcall','A'] - ],[ - ['set','x',333], - ['send','B',1], - ['pcall','B'] - ],[ - ['set','x',444], - ['send','A',1], - ['pcall','A'] - ],[ - ['set','x',555], - ['send','B',1] - ]]` - testStateRollback(t, bc, script, - map[string]int{"A": 0, "B": 0}, - map[string]int64{"A": 3, "B": 0}) + code := readLuaCode(t, file) + bc, err := LoadDummyChain(SetHardForkVersion(version)) + require.NoErrorf(t, err, "failed to create dummy chain") + defer bc.Release() + + // deploy and setup the name resolver + err = bc.ConnectBlock( + NewLuaTxAccount("user", 10, types.Aergo), + NewLuaTxDeploy("user", "resolver", 0, resolver), + NewLuaTxCall("user", "resolver", 0, fmt.Sprintf(`{"Name":"set","Args":["A","%s"]}`, nameToAddress("A"))), + NewLuaTxCall("user", "resolver", 0, fmt.Sprintf(`{"Name":"set","Args":["B","%s"]}`, nameToAddress("B"))), + NewLuaTxCall("user", "resolver", 0, fmt.Sprintf(`{"Name":"set","Args":["C","%s"]}`, nameToAddress("C"))), + NewLuaTxCall("user", "resolver", 0, fmt.Sprintf(`{"Name":"set","Args":["D","%s"]}`, nameToAddress("D"))), + NewLuaTxCall("user", "resolver", 0, fmt.Sprintf(`{"Name":"set","Args":["E","%s"]}`, nameToAddress("E"))), + ) + require.NoErrorf(t, err, "failed to deploy and setup resolver") + + // deploy the contracts + err = bc.ConnectBlock( + NewLuaTxDeploy("user", "A", 3, code).Constructor(fmt.Sprintf(`["%s","A"]`, nameToAddress("resolver"))), + NewLuaTxDeploy("user", "B", 0, code).Constructor(fmt.Sprintf(`["%s","B"]`, nameToAddress("resolver"))), + NewLuaTxDeploy("user", "C", 0, code).Constructor(fmt.Sprintf(`["%s","C"]`, nameToAddress("resolver"))), + ) + require.NoErrorf(t, err, "failed to deploy the contracts") + + // A -> A -> A (3 calls on the same contract) + + script := `[[ + ['set','x',111], + ['send','B',1], + ['pcall','A'] + ],[ + ['set','x',222], + ['send','C',1], + ['pcall','A'] + ],[ + ['set','x',333], + ['send','E',1] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 333}, + map[string]int64{"A": 0, "B": 1, "C": 1, "E": 1}) + + script = `[[ + ['set','x',111], + ['send','B',1], + ['pcall','A'] + ],[ + ['set','x',222], + ['send','C',1], + ['pcall','A'] + ],[ + ['set','x',333], + ['send','D',1], + ['fail'] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 222}, + map[string]int64{"A": 1, "B": 1, "C": 1, "D": 0}) + + script = `[[ + ['set','x',111], + ['send','B',1], + ['pcall','A'] + ],[ + ['set','x',222], + ['send','C',1], + ['pcall','A'], + ['fail'] + ],[ + ['set','x',333], + ['send','D',1] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111}, + map[string]int64{"A": 2, "B": 1, "C": 0, "D": 0}) + + script = `[[ + ['set','x',111], + ['send','B',1], + ['pcall','A'], + ['fail'] + ],[ + ['set','x',222], + ['send','C',1], + ['pcall','A'] + ],[ + ['set','x',333], + ['send','D',1] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 0}, + map[string]int64{"A": 3, "B": 0, "C": 0, "D": 0}) + + // A -> B -> C (3 different contracts) + + script = `[[ + ['set','x',111], + ['send','B',3], + ['pcall','B'] + ],[ + ['set','x',222], + ['send','C',2], + ['pcall','C'] + ],[ + ['set','x',333], + ['send','A',1] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 222, "C": 333}, + map[string]int64{"A": 1, "B": 1, "C": 1}) + + script = `[[ + ['set','x',111], + ['send','B',3], + ['pcall','B'] + ],[ + ['set','x',222], + ['send','C',2], + ['pcall','C'] + ],[ + ['set','x',333], + ['send','A',1], + ['fail'] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 222, "C": 0}, + map[string]int64{"A": 0, "B": 1, "C": 2}) + + script = `[[ + ['set','x',111], + ['send','B',3], + ['pcall','B'] + ],[ + ['set','x',222], + ['send','C',2], + ['pcall','C'], + ['fail'] + ],[ + ['set','x',333], + ['send','A',1] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 0, "C": 0}, + map[string]int64{"A": 0, "B": 3, "C": 0}) + + script = `[[ + ['set','x',111], + ['send','B',3], + ['pcall','B'], + ['fail'] + ],[ + ['set','x',222], + ['send','C',2], + ['pcall','C'] + ],[ + ['set','x',333], + ['send','A',1] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 0, "B": 0, "C": 0}, + map[string]int64{"A": 3, "B": 0, "C": 0}) + + // A -> B -> A (call back to original contract) + + script = `[[ + ['set','x',111], + ['send','B',3], + ['pcall','B'] + ],[ + ['set','x',222], + ['send','A',2], + ['pcall','A'] + ],[ + ['set','x',333], + ['send','B',1] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 333, "B": 222}, + map[string]int64{"A": 1, "B": 2}) + + script = `[[ + ['set','x',111], + ['send','B',3], + ['pcall','B'] + ],[ + ['set','x',222], + ['send','A',2], + ['pcall','A'] + ],[ + ['set','x',333], + ['send','B',1], + ['fail'] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 222}, + map[string]int64{"A": 2, "B": 1}) + + script = `[[ + ['set','x',111], + ['send','B',3], + ['pcall','B'] + ],[ + ['set','x',222], + ['send','A',2], + ['pcall','A'], + ['fail'] + ],[ + ['set','x',333], + ['send','B',1] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 0}, + map[string]int64{"A": 0, "B": 3}) + + script = `[[ + ['set','x',111], + ['send','B',3], + ['pcall','B'], + ['fail'] + ],[ + ['set','x',222], + ['send','A',2], + ['pcall','A'] + ],[ + ['set','x',333], + ['send','B',1] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 0, "B": 0}, + map[string]int64{"A": 3, "B": 0}) + + // A -> B -> B + + script = `[[ + ['set','x',111], + ['send','B',3], + ['pcall','B'] + ],[ + ['set','x',222], + ['send','C',1], + ['pcall','B'] + ],[ + ['set','x',333], + ['send','A',1] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 333}, + map[string]int64{"A": 1, "B": 1, "C": 1}) + + script = `[[ + ['set','x',111], + ['send','B',3], + ['pcall','B'] + ],[ + ['set','x',222], + ['send','C',1], + ['pcall','B'] + ],[ + ['set','x',333], + ['send','A',1], + ['fail'] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 222}, + map[string]int64{"A": 0, "B": 2, "C": 1}) + + script = `[[ + ['set','x',111], + ['send','B',3], + ['pcall','B'] + ],[ + ['set','x',222], + ['send','C',1], + ['pcall','B'], + ['fail'] + ],[ + ['set','x',333], + ['send','A',1] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 0}, + map[string]int64{"A": 0, "B": 3, "C": 0}) + + script = `[[ + ['set','x',111], + ['send','B',3], + ['pcall','B'], + ['fail'] + ],[ + ['set','x',222], + ['send','C',1], + ['pcall','B'] + ],[ + ['set','x',333], + ['send','A',1] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 0, "B": 0}, + map[string]int64{"A": 3, "B": 0, "C": 0}) + + // A -> A -> B + + script = `[[ + ['set','x',111], + ['send','B',2], + ['pcall','A'] + ],[ + ['set','x',222], + ['send','C',1], + ['pcall','B'] + ],[ + ['set','x',333], + ['send','A',1] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 222, "B": 333}, + map[string]int64{"A": 1, "B": 1, "C": 1}) + + script = `[[ + ['set','x',111], + ['send','B',2], + ['pcall','A'] + ],[ + ['set','x',222], + ['send','C',1], + ['pcall','B'] + ],[ + ['set','x',333], + ['send','A',1], + ['fail'] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 222, "B": 0}, + map[string]int64{"A": 0, "B": 2, "C": 1}) + + script = `[[ + ['set','x',111], + ['send','B',2], + ['pcall','A'] + ],[ + ['set','x',222], + ['send','C',1], + ['pcall','B'], + ['fail'] + ],[ + ['set','x',333], + ['send','A',1] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 0}, + map[string]int64{"A": 1, "B": 2, "C": 0}) + + script = `[[ + ['set','x',111], + ['send','B',2], + ['pcall','A'], + ['fail'] + ],[ + ['set','x',222], + ['send','C',1], + ['pcall','B'] + ],[ + ['set','x',333], + ['send','A',1] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 0, "B": 0}, + map[string]int64{"A": 3, "B": 0, "C": 0}) + + // A -> B -> A -> B -> A (zigzag) + + script = `[[ + ['set','x',111], + ['send','B',1], + ['pcall','B'] + ],[ + ['set','x',222], + ['send','A',1], + ['pcall','A'] + ],[ + ['set','x',333], + ['send','B',1], + ['pcall','B'] + ],[ + ['set','x',444], + ['send','A',1], + ['pcall','A'] + ],[ + ['set','x',555], + ['send','B',1] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 555, "B": 444}, + map[string]int64{"A": 2, "B": 1}) + + script = `[[ + ['set','x',111], + ['send','B',1], + ['pcall','B'] + ],[ + ['set','x',222], + ['send','A',1], + ['pcall','A'] + ],[ + ['set','x',333], + ['send','B',1], + ['pcall','B'] + ],[ + ['set','x',444], + ['send','A',1], + ['pcall','A'] + ],[ + ['set','x',555], + ['send','B',1], + ['fail'] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 333, "B": 444}, + map[string]int64{"A": 3, "B": 0}) + + script = `[[ + ['set','x',111], + ['send','B',1], + ['pcall','B'] + ],[ + ['set','x',222], + ['send','A',1], + ['pcall','A'] + ],[ + ['set','x',333], + ['send','B',1], + ['pcall','B'] + ],[ + ['set','x',444], + ['send','A',1], + ['pcall','A'], + ['fail'] + ],[ + ['set','x',555], + ['send','B',1] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 333, "B": 222}, + map[string]int64{"A": 2, "B": 1}) + + script = `[[ + ['set','x',111], + ['send','B',1], + ['pcall','B'] + ],[ + ['set','x',222], + ['send','A',1], + ['pcall','A'] + ],[ + ['set','x',333], + ['send','B',1], + ['pcall','B'], + ['fail'] + ],[ + ['set','x',444], + ['send','A',1], + ['pcall','A'] + ],[ + ['set','x',555], + ['send','B',1] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 222}, + map[string]int64{"A": 3, "B": 0}) + + script = `[[ + ['set','x',111], + ['send','B',1], + ['pcall','B'] + ],[ + ['set','x',222], + ['send','A',1], + ['pcall','A'], + ['fail'] + ],[ + ['set','x',333], + ['send','B',1], + ['pcall','B'] + ],[ + ['set','x',444], + ['send','A',1], + ['pcall','A'] + ],[ + ['set','x',555], + ['send','B',1] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 0}, + map[string]int64{"A": 2, "B": 1}) + + script = `[[ + ['set','x',111], + ['send','B',1], + ['pcall','B'], + ['fail'] + ],[ + ['set','x',222], + ['send','A',1], + ['pcall','A'] + ],[ + ['set','x',333], + ['send','B',1], + ['pcall','B'] + ],[ + ['set','x',444], + ['send','A',1], + ['pcall','A'] + ],[ + ['set','x',555], + ['send','B',1] + ]]` + testStateRollback(t, bc, script, + map[string]int{"A": 0, "B": 0}, + map[string]int64{"A": 3, "B": 0}) + + } } } @@ -3738,409 +3999,421 @@ func TestPcallStateRollback2(t *testing.T) { func TestPcallStateRollback3(t *testing.T) { t.Skip("disabled until bug with test is fixed") resolver := readLuaCode(t, "resolver.lua") - code := readLuaCode(t, "feature_pcall_rollback_4.lua") for version := min_version; version <= max_version; version++ { - bc, err := LoadDummyChain(SetHardForkVersion(version)) - require.NoErrorf(t, err, "failed to create dummy chain") - defer bc.Release() + files := make([]string, 0) + files = append(files, "feature_pcall_rollback_4a.lua") // contract.pcall + if version >= 4 { + files = append(files, "feature_pcall_rollback_4b.lua") // pcall + files = append(files, "feature_pcall_rollback_4c.lua") // xpcall + } - err = bc.ConnectBlock( - NewLuaTxAccount("user", 1, types.Aergo), - NewLuaTxDeploy("user", "resolver", 0, resolver), - NewLuaTxDeploy("user", "A", 0, code).Constructor(fmt.Sprintf(`["%s","A"]`, nameToAddress("resolver"))), - NewLuaTxDeploy("user", "B", 0, code).Constructor(fmt.Sprintf(`["%s","B"]`, nameToAddress("resolver"))), - NewLuaTxDeploy("user", "C", 0, code).Constructor(fmt.Sprintf(`["%s","C"]`, nameToAddress("resolver"))), - ) - require.NoErrorf(t, err, "failed to deploy") + // iterate over all files + for _, file := range files { - err = bc.ConnectBlock( - NewLuaTxCall("user", "resolver", 0, fmt.Sprintf(`{"Name":"set","Args":["A","%s"]}`, nameToAddress("A"))), - NewLuaTxCall("user", "resolver", 0, fmt.Sprintf(`{"Name":"set","Args":["B","%s"]}`, nameToAddress("B"))), - NewLuaTxCall("user", "resolver", 0, fmt.Sprintf(`{"Name":"set","Args":["C","%s"]}`, nameToAddress("C"))), - ) - require.NoErrorf(t, err, "failed to call resolver contract") - - // A -> A -> A (3 calls on the same contract) - - script := `[[ - ['db.set',111], - ['pcall','A'] - ],[ - ['db.set',222], - ['pcall','A'] - ],[ - ['db.set',333] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 333}) - - script = `[[ - ['db.set',111], - ['pcall','A'] - ],[ - ['db.set',222], - ['pcall','A'] - ],[ - ['db.set',333], - ['fail'] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 222}) - - script = `[[ - ['db.set',111], - ['pcall','A'] - ],[ - ['db.set',222], - ['pcall','A'], - ['fail'] - ],[ - ['db.set',333] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 111}) - - script = `[[ - ['db.set',111], - ['pcall','A'], - ['fail'] - ],[ - ['db.set',222], - ['pcall','A'] - ],[ - ['db.set',333] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 0}) - - // A -> B -> C (3 different contracts) - - script = `[[ - ['db.set',111], - ['pcall','B'] - ],[ - ['db.set',222], - ['pcall','C'] - ],[ - ['db.set',333] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 222, "C": 333}) - - script = `[[ - ['db.set',111], - ['pcall','B'] - ],[ - ['db.set',222], - ['pcall','C'] - ],[ - ['db.set',333], - ['fail'] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 222, "C": 0}) - - script = `[[ - ['db.set',111], - ['pcall','B'] - ],[ - ['db.set',222], - ['pcall','C'], - ['fail'] - ],[ - ['db.set',333] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 0, "C": 0}) - - script = `[[ - ['db.set',111], - ['pcall','B'], - ['fail'] - ],[ - ['db.set',222], - ['pcall','C'] - ],[ - ['db.set',333] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 0, "B": 0, "C": 0}) - - // A -> B -> A (call back to original contract) - - script = `[[ - ['db.set',111], - ['pcall','B'] - ],[ - ['db.set',222], - ['pcall','A'] - ],[ - ['db.set',333] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 333, "B": 222}) - - script = `[[ - ['db.set',111], - ['pcall','B'] - ],[ - ['db.set',222], - ['pcall','A'] - ],[ - ['db.set',333], - ['fail'] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 222}) - - script = `[[ - ['db.set',111], - ['pcall','B'] - ],[ - ['db.set',222], - ['pcall','A'], - ['fail'] - ],[ - ['db.set',333] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 0}) - - script = `[[ - ['db.set',111], - ['pcall','B'], - ['fail'] - ],[ - ['db.set',222], - ['pcall','A'] - ],[ - ['db.set',333] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 0, "B": 0}) - - // A -> B -> B - - script = `[[ - ['db.set',111], - ['pcall','B'] - ],[ - ['db.set',222], - ['pcall','B'] - ],[ - ['db.set',333] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 333}) - - script = `[[ - ['db.set',111], - ['pcall','B'] - ],[ - ['db.set',222], - ['pcall','B'] - ],[ - ['db.set',333], - ['fail'] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 222}) - - script = `[[ - ['db.set',111], - ['pcall','B'] - ],[ - ['db.set',222], - ['pcall','B'], - ['fail'] - ],[ - ['db.set',333] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 0}) - - script = `[[ - ['db.set',111], - ['pcall','B'], - ['fail'] - ],[ - ['db.set',222], - ['pcall','B'] - ],[ - ['db.set',333] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 0, "B": 0}) - - // A -> A -> B - - script = `[[ - ['db.set',111], - ['pcall','A'] - ],[ - ['db.set',222], - ['pcall','B'] - ],[ - ['db.set',333] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 222, "B": 333}) - - script = `[[ - ['db.set',111], - ['pcall','A'] - ],[ - ['db.set',222], - ['pcall','B'] - ],[ - ['db.set',333], - ['fail'] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 222, "B": 0}) - - script = `[[ - ['db.set',111], - ['pcall','A'] - ],[ - ['db.set',222], - ['pcall','B'], - ['fail'] - ],[ - ['db.set',333] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 0}) - - script = `[[ - ['db.set',111], - ['pcall','A'], - ['fail'] - ],[ - ['db.set',222], - ['pcall','B'] - ],[ - ['db.set',333] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 0, "B": 0}) - - // A -> B -> A -> B -> A (zigzag) - - script = `[[ - ['db.set',111], - ['pcall','B'] - ],[ - ['db.set',222], - ['pcall','A'] - ],[ - ['db.set',333], - ['pcall','B'] - ],[ - ['db.set',444], - ['pcall','A'] - ],[ - ['db.set',555] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 555, "B": 444}) - - script = `[[ - ['db.set',111], - ['pcall','B'] - ],[ - ['db.set',222], - ['pcall','A'] - ],[ - ['db.set',333], - ['pcall','B'] - ],[ - ['db.set',444], - ['pcall','A'] - ],[ - ['db.set',555], - ['fail'] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 333, "B": 444}) - - script = `[[ - ['db.set',111], - ['pcall','B'] - ],[ - ['db.set',222], - ['pcall','A'] - ],[ - ['db.set',333], - ['pcall','B'] - ],[ - ['db.set',444], - ['pcall','A'], - ['fail'] - ],[ - ['db.set',555] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 333, "B": 222}) - - script = `[[ - ['db.set',111], - ['pcall','B'] - ],[ - ['db.set',222], - ['pcall','A'] - ],[ - ['db.set',333], - ['pcall','B'], - ['fail'] - ],[ - ['db.set',444], - ['pcall','A'] - ],[ - ['db.set',555] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 222}) - - script = `[[ - ['db.set',111], - ['pcall','B'] - ],[ - ['db.set',222], - ['pcall','A'], - ['fail'] - ],[ - ['db.set',333], - ['pcall','B'] - ],[ - ['db.set',444], - ['pcall','A'] - ],[ - ['db.set',555] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 111, "B": 0}) - - script = `[[ - ['db.set',111], - ['pcall','B'], - ['fail'] - ],[ - ['db.set',222], - ['pcall','A'] - ],[ - ['db.set',333], - ['pcall','B'] - ],[ - ['db.set',444], - ['pcall','A'] - ],[ - ['db.set',555] - ]]` - testDbStateRollback(t, bc, script, - map[string]int{"A": 0, "B": 0}) + code := readLuaCode(t, file) + bc, err := LoadDummyChain(SetHardForkVersion(version)) + require.NoErrorf(t, err, "failed to create dummy chain") + defer bc.Release() + + err = bc.ConnectBlock( + NewLuaTxAccount("user", 1, types.Aergo), + NewLuaTxDeploy("user", "resolver", 0, resolver), + NewLuaTxDeploy("user", "A", 0, code).Constructor(fmt.Sprintf(`["%s","A"]`, nameToAddress("resolver"))), + NewLuaTxDeploy("user", "B", 0, code).Constructor(fmt.Sprintf(`["%s","B"]`, nameToAddress("resolver"))), + NewLuaTxDeploy("user", "C", 0, code).Constructor(fmt.Sprintf(`["%s","C"]`, nameToAddress("resolver"))), + ) + require.NoErrorf(t, err, "failed to deploy") + + err = bc.ConnectBlock( + NewLuaTxCall("user", "resolver", 0, fmt.Sprintf(`{"Name":"set","Args":["A","%s"]}`, nameToAddress("A"))), + NewLuaTxCall("user", "resolver", 0, fmt.Sprintf(`{"Name":"set","Args":["B","%s"]}`, nameToAddress("B"))), + NewLuaTxCall("user", "resolver", 0, fmt.Sprintf(`{"Name":"set","Args":["C","%s"]}`, nameToAddress("C"))), + ) + require.NoErrorf(t, err, "failed to call resolver contract") + + // A -> A -> A (3 calls on the same contract) + + script := `[[ + ['db.set',111], + ['pcall','A'] + ],[ + ['db.set',222], + ['pcall','A'] + ],[ + ['db.set',333] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 333}) + + script = `[[ + ['db.set',111], + ['pcall','A'] + ],[ + ['db.set',222], + ['pcall','A'] + ],[ + ['db.set',333], + ['fail'] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 222}) + + script = `[[ + ['db.set',111], + ['pcall','A'] + ],[ + ['db.set',222], + ['pcall','A'], + ['fail'] + ],[ + ['db.set',333] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 111}) + + script = `[[ + ['db.set',111], + ['pcall','A'], + ['fail'] + ],[ + ['db.set',222], + ['pcall','A'] + ],[ + ['db.set',333] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 0}) + + // A -> B -> C (3 different contracts) + + script = `[[ + ['db.set',111], + ['pcall','B'] + ],[ + ['db.set',222], + ['pcall','C'] + ],[ + ['db.set',333] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 222, "C": 333}) + + script = `[[ + ['db.set',111], + ['pcall','B'] + ],[ + ['db.set',222], + ['pcall','C'] + ],[ + ['db.set',333], + ['fail'] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 222, "C": 0}) + + script = `[[ + ['db.set',111], + ['pcall','B'] + ],[ + ['db.set',222], + ['pcall','C'], + ['fail'] + ],[ + ['db.set',333] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 0, "C": 0}) + + script = `[[ + ['db.set',111], + ['pcall','B'], + ['fail'] + ],[ + ['db.set',222], + ['pcall','C'] + ],[ + ['db.set',333] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 0, "B": 0, "C": 0}) + + // A -> B -> A (call back to original contract) + + script = `[[ + ['db.set',111], + ['pcall','B'] + ],[ + ['db.set',222], + ['pcall','A'] + ],[ + ['db.set',333] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 333, "B": 222}) + + script = `[[ + ['db.set',111], + ['pcall','B'] + ],[ + ['db.set',222], + ['pcall','A'] + ],[ + ['db.set',333], + ['fail'] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 222}) + + script = `[[ + ['db.set',111], + ['pcall','B'] + ],[ + ['db.set',222], + ['pcall','A'], + ['fail'] + ],[ + ['db.set',333] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 0}) + + script = `[[ + ['db.set',111], + ['pcall','B'], + ['fail'] + ],[ + ['db.set',222], + ['pcall','A'] + ],[ + ['db.set',333] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 0, "B": 0}) + + // A -> B -> B + + script = `[[ + ['db.set',111], + ['pcall','B'] + ],[ + ['db.set',222], + ['pcall','B'] + ],[ + ['db.set',333] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 333}) + + script = `[[ + ['db.set',111], + ['pcall','B'] + ],[ + ['db.set',222], + ['pcall','B'] + ],[ + ['db.set',333], + ['fail'] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 222}) + + script = `[[ + ['db.set',111], + ['pcall','B'] + ],[ + ['db.set',222], + ['pcall','B'], + ['fail'] + ],[ + ['db.set',333] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 0}) + + script = `[[ + ['db.set',111], + ['pcall','B'], + ['fail'] + ],[ + ['db.set',222], + ['pcall','B'] + ],[ + ['db.set',333] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 0, "B": 0}) + + // A -> A -> B + + script = `[[ + ['db.set',111], + ['pcall','A'] + ],[ + ['db.set',222], + ['pcall','B'] + ],[ + ['db.set',333] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 222, "B": 333}) + + script = `[[ + ['db.set',111], + ['pcall','A'] + ],[ + ['db.set',222], + ['pcall','B'] + ],[ + ['db.set',333], + ['fail'] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 222, "B": 0}) + + script = `[[ + ['db.set',111], + ['pcall','A'] + ],[ + ['db.set',222], + ['pcall','B'], + ['fail'] + ],[ + ['db.set',333] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 0}) + + script = `[[ + ['db.set',111], + ['pcall','A'], + ['fail'] + ],[ + ['db.set',222], + ['pcall','B'] + ],[ + ['db.set',333] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 0, "B": 0}) + + // A -> B -> A -> B -> A (zigzag) + + script = `[[ + ['db.set',111], + ['pcall','B'] + ],[ + ['db.set',222], + ['pcall','A'] + ],[ + ['db.set',333], + ['pcall','B'] + ],[ + ['db.set',444], + ['pcall','A'] + ],[ + ['db.set',555] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 555, "B": 444}) + + script = `[[ + ['db.set',111], + ['pcall','B'] + ],[ + ['db.set',222], + ['pcall','A'] + ],[ + ['db.set',333], + ['pcall','B'] + ],[ + ['db.set',444], + ['pcall','A'] + ],[ + ['db.set',555], + ['fail'] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 333, "B": 444}) + + script = `[[ + ['db.set',111], + ['pcall','B'] + ],[ + ['db.set',222], + ['pcall','A'] + ],[ + ['db.set',333], + ['pcall','B'] + ],[ + ['db.set',444], + ['pcall','A'], + ['fail'] + ],[ + ['db.set',555] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 333, "B": 222}) + + script = `[[ + ['db.set',111], + ['pcall','B'] + ],[ + ['db.set',222], + ['pcall','A'] + ],[ + ['db.set',333], + ['pcall','B'], + ['fail'] + ],[ + ['db.set',444], + ['pcall','A'] + ],[ + ['db.set',555] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 222}) + + script = `[[ + ['db.set',111], + ['pcall','B'] + ],[ + ['db.set',222], + ['pcall','A'], + ['fail'] + ],[ + ['db.set',333], + ['pcall','B'] + ],[ + ['db.set',444], + ['pcall','A'] + ],[ + ['db.set',555] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 111, "B": 0}) + + script = `[[ + ['db.set',111], + ['pcall','B'], + ['fail'] + ],[ + ['db.set',222], + ['pcall','A'] + ],[ + ['db.set',333], + ['pcall','B'] + ],[ + ['db.set',444], + ['pcall','A'] + ],[ + ['db.set',555] + ]]` + testDbStateRollback(t, bc, script, + map[string]int{"A": 0, "B": 0}) + + } } } diff --git a/contract/vm_state.go b/contract/vm_state.go index c3f613e72..27e2bd62f 100644 --- a/contract/vm_state.go +++ b/contract/vm_state.go @@ -88,6 +88,8 @@ type recoveryEntry struct { func (re *recoveryEntry) recovery(bs *state.BlockState) error { var zero big.Int cs := re.callState + + // restore the contract balance if re.amount.Cmp(&zero) > 0 { if re.senderState != nil { re.senderState.AddBalance(re.amount) @@ -99,6 +101,8 @@ func (re *recoveryEntry) recovery(bs *state.BlockState) error { if re.onlySend { return nil } + + // restore the contract nonce if re.senderState != nil { re.senderState.SetNonce(re.senderNonce) } @@ -106,19 +110,23 @@ func (re *recoveryEntry) recovery(bs *state.BlockState) error { if cs == nil { return nil } + + // restore the contract state if re.stateRevision != -1 { err := cs.ctrState.Rollback(re.stateRevision) if err != nil { return newDbSystemError(err) } if re.isDeploy { - err := cs.ctrState.SetCode(nil) + err := cs.ctrState.SetCode(nil, nil) if err != nil { return newDbSystemError(err) } bs.RemoveCache(cs.ctrState.GetAccountID()) } } + + // restore the contract SQL db state if cs.tx != nil { if re.sqlSaveName == nil { err := cs.tx.rollbackToSavepoint() @@ -133,6 +141,7 @@ func (re *recoveryEntry) recovery(bs *state.BlockState) error { } } } + return nil } diff --git a/go.mod b/go.mod index 599db1817..70a9f29bd 100644 --- a/go.mod +++ b/go.mod @@ -167,6 +167,7 @@ require ( golang.org/x/text v0.13.0 // indirect golang.org/x/tools v0.14.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 // indirect google.golang.org/grpc/examples v0.0.0-20230724170852-2aa261560586 // indirect gopkg.in/yaml.v2 v2.3.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index a3ded517e..cf6966fd7 100644 --- a/go.sum +++ b/go.sum @@ -1276,6 +1276,8 @@ google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 h1:rNBFJjBCOgVr9pWD7rs/knKL4FRTKgpZmsRfV214zcA= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0/go.mod h1:Dk1tviKTvMCz5tvh7t+fh94dhmQVHuCt2OzJB3CTW9Y= google.golang.org/grpc/examples v0.0.0-20230724170852-2aa261560586 h1:3cBl7oDZlRZ9VAnaA9QglQNOY+ZP2wZJyGpiz1uuAuU= google.golang.org/grpc/examples v0.0.0-20230724170852-2aa261560586/go.mod h1:YYPcVQPFEuZQrEwqV6D//WM2s4HnWXtvFr/kII5NKbI= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= diff --git a/internal/enc/hex/hex.go b/internal/enc/hex/hex.go index 6c484ee4d..fa2ea8525 100644 --- a/internal/enc/hex/hex.go +++ b/internal/enc/hex/hex.go @@ -9,3 +9,17 @@ func Encode(b []byte) string { func Decode(s string) ([]byte, error) { return hex.DecodeString(s) } + +func IsHexString(s string) bool { + // check is the input has even number of characters + if len(s)%2 != 0 { + return false + } + // check if the input contains only hex characters + for _, c := range s { + if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) { + return false + } + } + return true +} diff --git a/libtool/src/luajit b/libtool/src/luajit index d64278c3b..3a1fec133 160000 --- a/libtool/src/luajit +++ b/libtool/src/luajit @@ -1 +1 @@ -Subproject commit d64278c3bac3dedb74f7117231ab996ce5630b15 +Subproject commit 3a1fec1334431340038a5a6c62e65be1f7a36f78 diff --git a/mempool/mempool.go b/mempool/mempool.go index bb490544b..87815a6e1 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -35,6 +35,7 @@ import ( "github.com/aergoio/aergo/v2/state/statedb" "github.com/aergoio/aergo/v2/types" "github.com/aergoio/aergo/v2/types/message" + "github.com/aergoio/aergo/v2/blacklist" ) const ( @@ -74,6 +75,7 @@ type MemPool struct { acceptChainIdHash []byte isPublic bool whitelist *whitelistConf + blockDeploy bool // followings are for test testConfig bool deadtx int @@ -102,6 +104,7 @@ func NewMemPoolService(cfg *cfg.Config, cs *chain.ChainService) *MemPool { status: initial, verifier: nil, quit: make(chan bool), + blockDeploy: cfg.Mempool.BlockDeploy, } actor.BaseComponent = component.NewBaseComponent(message.MemPoolSvc, actor, log.NewLogger("mempool")) if cfg.Mempool.EnableFadeout == false { @@ -109,6 +112,9 @@ func NewMemPoolService(cfg *cfg.Config, cs *chain.ChainService) *MemPool { } else if cfg.Mempool.FadeoutPeriod > 0 { evictPeriod = time.Duration(cfg.Mempool.FadeoutPeriod) * time.Hour } + if cfg.Mempool.Blacklist != nil { + blacklist.Initialize(cfg.Mempool.Blacklist) + } return actor } @@ -607,6 +613,7 @@ func (mp *MemPool) nextBlockVersion() int32 { } // check tx sanity +// check if sender is on blacklist // check if sender has enough balance // check if recipient is valid name // check tx account is lower than known value @@ -614,6 +621,9 @@ func (mp *MemPool) validateTx(tx types.Transaction, account types.Address) error if !mp.whitelist.Check(types.EncodeAddress(account)) { return types.ErrTxNotAllowedAccount } + if blacklist.Check(types.EncodeAddress(account)) { + return types.ErrTxNotAllowedAccount + } ns, err := mp.getAccountState(account) if err != nil { return err @@ -657,6 +667,9 @@ func (mp *MemPool) validateTx(tx types.Transaction, account types.Address) error if tx.GetBody().GetRecipient() != nil { return types.ErrTxInvalidRecipient } + if mp.blockDeploy { + return types.ErrTxInvalidType + } case types.TxType_GOVERNANCE: id := tx.GetBody().GetRecipient() aergoState, err := mp.getAccountState(id) diff --git a/rpc/grpcserver.go b/rpc/grpcserver.go index 0dd0ea9b7..a6253b18f 100644 --- a/rpc/grpcserver.go +++ b/rpc/grpcserver.go @@ -1067,84 +1067,6 @@ func (rpc *AergoRPCService) GetReceipt(ctx context.Context, in *types.SingleByte return rsp.Receipt, rsp.Err } -func (rpc *AergoRPCService) GetReceipts(ctx context.Context, in *types.ReceiptsParams) (*types.ReceiptsPaged, error) { - if err := rpc.checkAuth(ctx, ReadBlockChain); err != nil { - return nil, err - } - - var result interface{} - var err error - if cap(in.Hashornumber) == 0 { - return nil, status.Errorf(codes.InvalidArgument, "Received no bytes") - } - - if len(in.Hashornumber) == 32 { - result, err = rpc.hub.RequestFuture(message.ChainSvc, &message.GetReceipts{BlockHash: in.Hashornumber}, - defaultActorTimeout, "rpc.(*AergoRPCService).GetReceipts#2").Result() - } else if len(in.Hashornumber) == 8 { - number := uint64(binary.LittleEndian.Uint64(in.Hashornumber)) - result, err = rpc.hub.RequestFuture(message.ChainSvc, &message.GetReceiptsByNo{BlockNo: number}, - defaultActorTimeout, "rpc.(*AergoRPCService).GetReceipts#1").Result() - } else { - return nil, status.Errorf(codes.InvalidArgument, "Invalid input. Should be a 32 byte hash or up to 8 byte number.") - } - - if err != nil { - return nil, err - } - - getPaging := func(data *types.Receipts, size uint32, offset uint32) *types.ReceiptsPaged { - allReceipts := data.Get() - total := uint32(len(allReceipts)) - - var fetchSize uint32 - if size > uint32(1000) { - fetchSize = uint32(1000) - } else if size == uint32(0) { - fetchSize = 100 - } else { - fetchSize = size - } - - var receipts []*types.Receipt - if offset >= uint32(len(allReceipts)) { - receipts = []*types.Receipt{} - } else { - limit := offset + fetchSize - if limit > uint32(len(allReceipts)) { - limit = uint32(len(allReceipts)) - } - receipts = allReceipts[offset:limit] - } - - return &types.ReceiptsPaged{ - Receipts: receipts, - BlockNo: data.GetBlockNo(), - Total: total, - Size: fetchSize, - Offset: offset, - } - } - - switch result.(type) { - case message.GetReceiptsRsp: - rsp, ok := result.(message.GetReceiptsRsp) - if !ok { - return nil, status.Errorf(codes.Internal, "internal type (%v) error", reflect.TypeOf(result)) - } - return getPaging(rsp.Receipts, in.Paging.Size, in.Paging.Offset), rsp.Err - - case message.GetReceiptsByNoRsp: - rsp, ok := result.(message.GetReceiptsByNoRsp) - if !ok { - return nil, status.Errorf(codes.Internal, "internal type (%v) error", reflect.TypeOf(result)) - } - return getPaging(rsp.Receipts, in.Paging.Size, in.Paging.Offset), rsp.Err - } - - return nil, status.Errorf(codes.Internal, "unexpected result type %s, expected %s", reflect.TypeOf(result), "message.GetReceipts") -} - func (rpc *AergoRPCService) GetABI(ctx context.Context, in *types.SingleBytes) (*types.ABI, error) { if err := rpc.checkAuth(ctx, ReadBlockChain); err != nil { return nil, err diff --git a/rpc/web3/v1.go b/rpc/web3/v1.go index 92272aa1d..53ddf89d6 100644 --- a/rpc/web3/v1.go +++ b/rpc/web3/v1.go @@ -42,7 +42,6 @@ func (api *Web3APIv1) NewHandler() { "/getBlockMetadata": api.GetBlockMetadata, "/getTx": api.GetTX, "/getReceipt": api.GetReceipt, - "/getReceipts": api.GetReceipts, "/queryContract": api.QueryContract, "/listEvents": api.ListEvents, "/getABI": api.GetABI, @@ -667,80 +666,6 @@ func (api *Web3APIv1) GetReceipt() (handler http.Handler, ok bool) { return stringResponseHandler(jsonrpc.MarshalJSON(result), nil), true } -func (api *Web3APIv1) GetReceipts() (handler http.Handler, ok bool) { - values, err := url.ParseQuery(api.request.URL.RawQuery) - if err != nil { - return commonResponseHandler(&types.Empty{}, err), true - } - - request := &types.ReceiptsParams{} - request.Paging = &types.PageParams{} - - hash := values.Get("hash") - if hash != "" { - hashBytes, err := base58.Decode(hash) - if err != nil { - return commonResponseHandler(&types.Empty{}, err), true - } - request.Hashornumber = hashBytes - } - - number := values.Get("number") - if number != "" { - numberValue, err := strconv.ParseUint(number, 10, 64) - if err != nil { - return commonResponseHandler(&types.Empty{}, err), true - } - number := uint64(numberValue) // Replace with your actual value - byteValue := make([]byte, 8) - binary.LittleEndian.PutUint64(byteValue, number) - request.Hashornumber = byteValue - } - - size := values.Get("size") - if size != "" { - sizeValue, parseErr := strconv.ParseUint(size, 10, 64) - if parseErr != nil { - return commonResponseHandler(&types.Empty{}, parseErr), true - - } - request.Paging.Size = uint32(sizeValue) - if request.Paging.Size > 100 { - request.Paging.Size = 100 - } - } - - offset := values.Get("offset") - if offset != "" { - offsetValue, parseErr := strconv.ParseUint(offset, 10, 64) - if parseErr != nil { - return commonResponseHandler(&types.Empty{}, parseErr), true - } - request.Paging.Offset = uint32(offsetValue) - } - - result, err := api.rpc.GetReceipts(api.request.Context(), request) - if err != nil { - errStr := err.Error() - - strBlockNo := strings.TrimPrefix(errStr, "empty : blockNo=") - if strBlockNo == errStr { - return commonResponseHandler(&types.Empty{}, err), true - } - - blockNo, err := strconv.ParseUint(strBlockNo, 10, 64) - if err != nil { - return commonResponseHandler(&types.Empty{}, err), true - } - - receipts := &jsonrpc.InOutReceipts{} - receipts.BlockNo = blockNo - return stringResponseHandler(jsonrpc.MarshalJSON(receipts), nil), true - } - - output := jsonrpc.ConvReceiptsPaged(result) - return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true -} func (api *Web3APIv1) GetTX() (handler http.Handler, ok bool) { values, err := url.ParseQuery(api.request.URL.RawQuery) diff --git a/state/account.go b/state/account.go index a2948721a..973aa2a65 100644 --- a/state/account.go +++ b/state/account.go @@ -102,7 +102,7 @@ func (as *AccountState) IsNew() bool { } func (as *AccountState) IsContract() bool { - return len(as.State().CodeHash) > 0 + return len(as.State().CodeHash) > 0 || len(as.State().SourceHash) > 0 } func (as *AccountState) IsDeploy() bool { diff --git a/state/statedb/contract.go b/state/statedb/contract.go index 8a4731c3b..75dc70fd0 100644 --- a/state/statedb/contract.go +++ b/state/statedb/contract.go @@ -1,8 +1,6 @@ package statedb import ( - "bytes" - "github.com/aergoio/aergo-lib/db" "github.com/aergoio/aergo/v2/internal/common" "github.com/aergoio/aergo/v2/internal/enc/proto" @@ -18,17 +16,33 @@ type ContractState struct { store db.DB } -func (cs *ContractState) SetCode(code []byte) error { - codeHash := common.Hasher(code) - storedCode, err := cs.GetRawKV(codeHash[:]) - if err == nil && !bytes.Equal(code, storedCode) { - err = cs.SetRawKV(codeHash[:], code) - } +func (cs *ContractState) SetCode(sourceCode []byte, bytecode []byte) error { + var err error + + // hash the bytecode + bytecodeHash := common.Hasher(bytecode) + // save the bytecode to the database + err = cs.SetRawKV(bytecodeHash[:], bytecode) if err != nil { return err } - cs.State.CodeHash = codeHash[:] - cs.code = code + // update the contract state + cs.State.CodeHash = bytecodeHash[:] + // update the contract bytecode + cs.code = bytecode + + if sourceCode != nil { + // hash the source code + sourceCodeHash := common.Hasher(sourceCode) + // save the source code to the database + err = cs.SetRawKV(sourceCodeHash[:], sourceCode) + if err != nil { + return err + } + // update the contract state + cs.State.SourceHash = sourceCodeHash[:] + } + return nil } @@ -47,13 +61,28 @@ func (cs *ContractState) GetCode() ([]byte, error) { // not defined. do nothing. return nil, nil } - err := loadData(cs.store, cs.State.GetCodeHash(), &cs.code) + // load the code into the contract state + err := loadData(cs.store, codeHash, &cs.code) if err != nil { return nil, err } return cs.code, nil } +func (cs *ContractState) GetSourceCode() []byte { + sourceCodeHash := cs.GetSourceHash() + if sourceCodeHash == nil { + return nil + } + // load the source code from the database + var sourceCode []byte + err := loadData(cs.store, sourceCodeHash, &sourceCode) + if err != nil { + return nil + } + return sourceCode +} + func (cs *ContractState) GetAccountID() types.AccountID { return cs.account } diff --git a/state/statedb/contract_test.go b/state/statedb/contract_test.go index 6761e1608..ebaa74f84 100644 --- a/state/statedb/contract_test.go +++ b/state/statedb/contract_test.go @@ -17,7 +17,7 @@ func TestContractStateCode(t *testing.T) { assert.NoError(t, err, "could not open contract state") // set code - err = contractState.SetCode(testBytes) + err = contractState.SetCode(nil, testBytes) assert.NoError(t, err, "set code to contract state") // get code diff --git a/tests/common.sh b/tests/common.sh index c58e1ed9c..c7da4c6ea 100644 --- a/tests/common.sh +++ b/tests/common.sh @@ -69,12 +69,12 @@ wait_version() { get_deploy_args() { contract_file=$1 - #if [ "$fork_version" -ge "4" ]; then - # deploy_args="$contract_file" - #else + if [ "$fork_version" -ge "4" ]; then + deploy_args="$contract_file" + else ../bin/aergoluac --payload $contract_file > payload.out deploy_args="--payload `cat payload.out`" - #fi + fi } diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 213559641..8c12b4738 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -130,6 +130,9 @@ check ./test-gas-bf.sh check ./test-gas-verify-proof.sh check ./test-gas-per-function-v2.sh check ./test-contract-deploy.sh +check ./test-pcall-events.sh +check ./test-transaction-types.sh +check ./test-name-service.sh # change the hardfork version set_version 3 @@ -142,6 +145,9 @@ check ./test-gas-bf.sh check ./test-gas-verify-proof.sh check ./test-gas-per-function-v3.sh check ./test-contract-deploy.sh +check ./test-pcall-events.sh +check ./test-transaction-types.sh +check ./test-name-service.sh # change the hardfork version set_version 4 @@ -154,6 +160,9 @@ check ./test-gas-bf.sh check ./test-gas-verify-proof.sh check ./test-gas-per-function-v4.sh check ./test-contract-deploy.sh +check ./test-pcall-events.sh +check ./test-transaction-types.sh +check ./test-name-service.sh check ./test-multicall.sh # terminate the server process diff --git a/tests/test-gas-bf.sh b/tests/test-gas-bf.sh index 21cdbe41b..bcffba363 100755 --- a/tests/test-gas-bf.sh +++ b/tests/test-gas-bf.sh @@ -6,7 +6,11 @@ fork_version=$1 echo "-- deploy --" -deploy ../contract/vm_dummy/test_files/gas_bf.lua +if [ "$fork_version" -eq "4" ]; then + deploy ../contract/vm_dummy/test_files/gas_bf_v4.lua +else + deploy ../contract/vm_dummy/test_files/gas_bf_v2.lua +fi get_receipt $txhash @@ -32,7 +36,7 @@ assert_equals "$status" "SUCCESS" #assert_equals "$ret" "" if [ "$fork_version" -eq "4" ]; then - assert_equals "$gasUsed" "57105265" + assert_equals "$gasUsed" "47342481" elif [ "$fork_version" -eq "3" ]; then assert_equals "$gasUsed" "47456046" else diff --git a/tests/test-gas-op.sh b/tests/test-gas-op.sh index 668f75df7..ac4d3e448 100755 --- a/tests/test-gas-op.sh +++ b/tests/test-gas-op.sh @@ -32,7 +32,7 @@ assert_equals "$status" "SUCCESS" #assert_equals "$ret" "" if [ "$fork_version" -eq "4" ]; then - assert_equals "$gasUsed" "130048" + assert_equals "$gasUsed" "120832" else assert_equals "$gasUsed" "117610" fi diff --git a/tests/test-gas-per-function-v4.sh b/tests/test-gas-per-function-v4.sh index 6293c517b..89701753b 100755 --- a/tests/test-gas-per-function-v4.sh +++ b/tests/test-gas-per-function-v4.sh @@ -61,26 +61,19 @@ add_test "loop_n_branche_ops" 146372 add_test "function_header_ops" 143016 add_test "assert" 143146 -add_test "getfenv" 143041 -add_test "metatable" 143988 add_test "ipairs" 143039 add_test "pairs" 143039 add_test "next" 143087 -add_test "rawequal" 143216 -add_test "rawget" 143087 -add_test "rawset" 143941 add_test "select" 143166 -add_test "setfenv" 143076 add_test "tonumber" 143186 add_test "tostring" 143457 add_test "type" 143285 add_test "unpack" 150745 -add_test "pcall" 146165 -add_test "xpcall" 146437 +add_test "pcall" 147905 +add_test "xpcall" 148177 add_test "string.byte" 157040 add_test "string.char" 160397 -add_test "string.dump" 150349 add_test "string.find" 147808 add_test "string.format" 143764 add_test "string.gmatch" 143799 diff --git a/tests/test-name-service.lua b/tests/test-name-service.lua new file mode 100644 index 000000000..fb8046297 --- /dev/null +++ b/tests/test-name-service.lua @@ -0,0 +1,6 @@ + +function resolve(name) + return name_service.resolve(name) +end + +abi.register(resolve) diff --git a/tests/test-name-service.sh b/tests/test-name-service.sh new file mode 100755 index 000000000..8fdb8db29 --- /dev/null +++ b/tests/test-name-service.sh @@ -0,0 +1,183 @@ +set -e +source common.sh + +fork_version=$1 + + +echo "-- deploy contract --" + +deploy test-name-service.lua + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +address=$(cat receipt.json | jq .contractAddress | sed 's/"//g') + +assert_equals "$status" "CREATED" + + +echo "-- call contract with an invalid address --" + +txhash=$(../bin/aergocli --keystore . contract call AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R \ + ${address} resolve '["AmgExqUu6J4Za8VjyWMJANxoRaUvwgngGQJgemHgwWvuRSEd3wnX"]' \ + --password bmttest | jq .hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +ret=$(cat receipt.json | jq .ret | sed 's/"//g') +gasUsed=$(cat receipt.json | jq .gasUsed | sed 's/"//g') + +if [ "$fork_version" -eq "4" ]; then + assert_equals "$status" "ERROR" + # assert_equals "$ret" "[Contract.LuaResolve] Data and checksum don't match" + assert_contains "$ret" "Data and checksum don't match" +else + assert_equals "$status" "ERROR" + assert_contains "$ret" "attempt to index global 'name_service'" +fi + + +echo "-- call contract with a valid address --" + +txhash=$(../bin/aergocli --keystore . contract call AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R \ + ${address} resolve '["AmgExqUu6J4Za8VjyWMJANxoRaUvwgngGQJgemHgwWvuRSEd3wnE"]' \ + --password bmttest | jq .hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +ret=$(cat receipt.json | jq .ret | sed 's/"//g') + +if [ "$fork_version" -eq "4" ]; then + assert_equals "$status" "SUCCESS" + assert_equals "$ret" "AmgExqUu6J4Za8VjyWMJANxoRaUvwgngGQJgemHgwWvuRSEd3wnE" +else + assert_equals "$status" "ERROR" + assert_contains "$ret" "attempt to index global 'name_service'" +fi + + +echo "-- call contract with invalid name --" + +txhash=$(../bin/aergocli --keystore . contract call AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R \ + ${address} resolve '["long_name-with-invalid.chars"]' \ + --password bmttest | jq .hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +ret=$(cat receipt.json | jq .ret | sed 's/"//g') + +if [ "$fork_version" -eq "4" ]; then + assert_equals "$status" "SUCCESS" + assert_equals "$ret" "" +else + assert_equals "$status" "ERROR" + assert_contains "$ret" "attempt to index global 'name_service'" +fi + + +echo "-- call contract with valid but not set name --" + +txhash=$(../bin/aergocli --keystore . contract call AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R \ + ${address} resolve '["testnametest"]' --password bmttest | jq .hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +ret=$(cat receipt.json | jq .ret | sed 's/"//g') + +if [ "$fork_version" -eq "4" ]; then + assert_equals "$status" "SUCCESS" + assert_equals "$ret" "" +else + assert_equals "$status" "ERROR" + assert_contains "$ret" "attempt to index global 'name_service'" +fi + + +# use a different account name for each hardfork +account_name="testnamever$fork_version" +# later, it could also: +# - drop the account name, to recreate it later +# - let the account name to expire, by forwarding time + + +echo "-- register a new account name --" + +txhash=$(../bin/aergocli --keystore . name new --name="$account_name" \ + --from AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R \ + --password bmttest | jq .hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') + +assert_equals "$status" "SUCCESS" + + +echo "-- call contract --" + +txhash=$(../bin/aergocli --keystore . contract call AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R \ + ${address} resolve '["'$account_name'"]' --password bmttest | jq .hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +ret=$(cat receipt.json | jq .ret | sed 's/"//g') + +if [ "$fork_version" -eq "4" ]; then + assert_equals "$status" "SUCCESS" + assert_equals "$ret" "AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R" +else + assert_equals "$status" "ERROR" + assert_contains "$ret" "attempt to index global 'name_service'" +fi + + +echo "-- transfer the name --" + +txhash=$(../bin/aergocli --keystore . name update --name="$account_name" \ + --from AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R \ + --to Amh9vfP5My5DpSafe3gcZ1u8DiZNuqHSN2oAWehZW1kgB3XP4kPi \ + --password bmttest | jq .hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') + +assert_equals "$status" "SUCCESS" + + +echo "-- call contract --" + +txhash=$(../bin/aergocli --keystore . contract call AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R \ + ${address} resolve '["'$account_name'"]' --password bmttest | jq .hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +ret=$(cat receipt.json | jq .ret | sed 's/"//g') + +if [ "$fork_version" -eq "4" ]; then + assert_equals "$status" "SUCCESS" + assert_equals "$ret" "Amh9vfP5My5DpSafe3gcZ1u8DiZNuqHSN2oAWehZW1kgB3XP4kPi" +else + assert_equals "$status" "ERROR" + assert_contains "$ret" "attempt to index global 'name_service'" +fi + + +echo "-- query the contract --" + +../bin/aergocli contract query ${address} resolve '["'$account_name'"]' > result.txt 2> result.txt || true +result=$(cat result.txt) + +if [ "$fork_version" -eq "4" ]; then + result=$(echo $result | sed 's/"//g' | sed 's/\\//g' | sed 's/value://g') + assert_equals "$result" "Amh9vfP5My5DpSafe3gcZ1u8DiZNuqHSN2oAWehZW1kgB3XP4kPi" +else + assert_contains "$result" "Error: failed to query contract" + assert_contains "$result" "attempt to index global 'name_service'" +fi diff --git a/tests/test-pcall-events.sh b/tests/test-pcall-events.sh new file mode 100755 index 000000000..206f83f9f --- /dev/null +++ b/tests/test-pcall-events.sh @@ -0,0 +1,137 @@ +set -e +source common.sh + +fork_version=$1 + + +echo "-- deploy --" + +deploy ../contract/vm_dummy/test_files/pcall-events-3.lua + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +address3=$(cat receipt.json | jq .contractAddress | sed 's/"//g') + +assert_equals "$status" "CREATED" + + +get_deploy_args ../contract/vm_dummy/test_files/pcall-events-2.lua + +txhash=$(../bin/aergocli --keystore . --password bmttest \ + contract deploy AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R \ + $deploy_args '["'$address3'"]' | jq .hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +address2=$(cat receipt.json | jq .contractAddress | sed 's/"//g') + +assert_equals "$status" "CREATED" + + +get_deploy_args ../contract/vm_dummy/test_files/pcall-events-1.lua + +txhash=$(../bin/aergocli --keystore . --password bmttest \ + contract deploy AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R \ + $deploy_args '["'$address2'"]' | jq .hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +address1=$(cat receipt.json | jq .contractAddress | sed 's/"//g') + +assert_equals "$status" "CREATED" + + +get_deploy_args ../contract/vm_dummy/test_files/pcall-events-0.lua + +txhash=$(../bin/aergocli --keystore . --password bmttest \ + contract deploy AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R \ + $deploy_args '["'$address2'"]' | jq .hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +address0=$(cat receipt.json | jq .contractAddress | sed 's/"//g') + +assert_equals "$status" "CREATED" + + +echo "-- pcall --" + +txhash=$(../bin/aergocli --keystore . --password bmttest \ + contract call AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R \ + $address1 test_pcall "[]" | jq .hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +ret=$(cat receipt.json | jq .ret | sed 's/"//g') +gasUsed=$(cat receipt.json | jq .gasUsed | sed 's/"//g') +nevents=$(cat receipt.json | jq '.events | length') + +assert_equals "$status" "SUCCESS" +#assert_equals "$ret" "{}" + +if [ "$fork_version" -eq "4" ]; then + assert_equals "$nevents" "2" +else + assert_equals "$nevents" "6" +fi + + +echo "-- xpcall --" + +txhash=$(../bin/aergocli --keystore . --password bmttest \ + contract call AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R \ + $address1 test_xpcall "[]" | jq .hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +ret=$(cat receipt.json | jq .ret | sed 's/"//g') +gasUsed=$(cat receipt.json | jq .gasUsed | sed 's/"//g') +nevents=$(cat receipt.json | jq '.events | length') + +assert_equals "$status" "SUCCESS" +#assert_equals "$ret" "{}" + +if [ "$fork_version" -eq "4" ]; then + assert_equals "$nevents" "2" +else + assert_equals "$nevents" "6" +fi + + +echo "-- contract.pcall --" + +txhash=$(../bin/aergocli --keystore . --password bmttest \ + contract call AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R \ + $address1 test_contract_pcall "[]" | jq .hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +ret=$(cat receipt.json | jq .ret | sed 's/"//g') +gasUsed=$(cat receipt.json | jq .gasUsed | sed 's/"//g') +nevents=$(cat receipt.json | jq '.events | length') + +assert_equals "$status" "SUCCESS" +#assert_equals "$ret" "{}" + +if [ "$fork_version" -eq "4" ]; then + assert_equals "$nevents" "2" +else + assert_equals "$nevents" "6" +fi + + +#echo "----------- contract-1 event list ------------" +#aergocli event list --address $address1 --recent 1000 + +#echo "----------- contract-2 event list ------------" +#aergocli event list --address $address2 --recent 1000 + +#echo "----------- contract-3 event list ------------" +#aergocli event list --address $address3 --recent 1000 diff --git a/tests/test-transaction-types.sh b/tests/test-transaction-types.sh new file mode 100755 index 000000000..e08259fb4 --- /dev/null +++ b/tests/test-transaction-types.sh @@ -0,0 +1,415 @@ +set -e +source common.sh + +fork_version=$1 + + +# contract without "default" function +cat > test-tx-type-1.lua << EOF +function default() + return system.getAmount() +end + +function default2() + return system.getAmount() +end + +abi.payable(default2) +EOF + +# contract with not payable "default" function +cat > test-tx-type-2.lua << EOF +function default() + return system.getAmount() +end + +abi.register(default) +EOF + +# contract with payable "default" function +cat > test-tx-type-3.lua << EOF +function default() + return system.getAmount() +end + +abi.payable(default) +EOF + +echo "-- deploy 1 --" +deploy test-tx-type-1.lua +get_receipt $txhash +status=$(cat receipt.json | jq .status | sed 's/"//g') +no_default=$(cat receipt.json | jq .contractAddress | sed 's/"//g') +assert_equals "$status" "CREATED" + +echo "-- deploy 2 --" +deploy test-tx-type-2.lua +get_receipt $txhash +status=$(cat receipt.json | jq .status | sed 's/"//g') +not_payable=$(cat receipt.json | jq .contractAddress | sed 's/"//g') +assert_equals "$status" "CREATED" + +echo "-- deploy 3 --" +deploy test-tx-type-3.lua +get_receipt $txhash +status=$(cat receipt.json | jq .status | sed 's/"//g') +payable_default=$(cat receipt.json | jq .contractAddress | sed 's/"//g') +assert_equals "$status" "CREATED" + +# delete the contract files +rm test-tx-type-*.lua + + +# get some info +from=AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R +chainIdHash=$(../bin/aergocli blockchain | jq -r '.chainIdHash') + + +echo "-- TRANSFER type, contract without 'default' function --" + +txhash=$(../bin/aergocli --keystore . --password bmttest \ + sendtx --from $from --to $no_default --amount 1aergo \ + | jq .hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +ret=$(cat receipt.json | jq .ret | sed 's/"//g') +gasUsed=$(cat receipt.json | jq .gasUsed | sed 's/"//g') + +assert_equals "$status" "ERROR" +assert_equals "$ret" "'default' is not payable" +#assert_equals "$gasUsed" "117861" + + +echo "-- TRANSFER type, contract with not payable 'default' function --" + +txhash=$(../bin/aergocli --keystore . --password bmttest \ + sendtx --from $from --to $not_payable --amount 1aergo \ + | jq .hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +ret=$(cat receipt.json | jq .ret | sed 's/"//g') +gasUsed=$(cat receipt.json | jq .gasUsed | sed 's/"//g') + +assert_equals "$status" "ERROR" +assert_equals "$ret" "'default' is not payable" +#assert_equals "$gasUsed" "117861" + + +echo "-- TRANSFER type, contract with payable 'default' function --" + +txhash=$(../bin/aergocli --keystore . --password bmttest \ + sendtx --from $from --to $payable_default --amount 1aergo \ + | jq .hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +ret=$(cat receipt.json | jq .ret | sed 's/"//g') +gasUsed=$(cat receipt.json | jq .gasUsed | sed 's/"//g') + +assert_equals "$status" "SUCCESS" +assert_equals "$ret" "1000000000000000000" +#assert_equals "$gasUsed" "117861" + + +nonce=$(../bin/aergocli getstate --address $from | jq -r '.nonce') + +#echo "-- TRANSFER type, trying to make a call --" + + +echo "-- NORMAL type, contract without 'default' function (not sending) --" + +nonce=$((nonce + 1)) + +#"Payload": "'$from'", + +jsontx='{ +"Account": "'$from'", +"Recipient": "'$no_default'", +"Amount": "0", +"Type": 0, +"Nonce": '$nonce', +"chainIdHash": "'$chainIdHash'"}' + +jsontx=$(../bin/aergocli --keystore . --password bmttest \ + signtx --address $from --jsontx "$jsontx" ) + +txhash=$(../bin/aergocli committx --jsontx "$jsontx" | jq .results[0].hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +ret=$(cat receipt.json | jq .ret | sed 's/"//g') +gasUsed=$(cat receipt.json | jq .gasUsed | sed 's/"//g') + +if [ "$fork_version" -ge "4" ]; then + assert_equals "$status" "ERROR" + assert_equals "$ret" "tx not allowed recipient" + #assert_equals "$gasUsed" "117861" +else + assert_equals "$status" "ERROR" + assert_equals "$ret" "undefined function: default" + #assert_equals "$gasUsed" "117861" +fi + + +echo "-- NORMAL type, contract without 'default' function (sending) --" + +nonce=$((nonce + 1)) + +#"Payload": "'$from'", + +jsontx='{ +"Account": "'$from'", +"Recipient": "'$no_default'", +"Amount": "1.23aergo", +"Type": 0, +"Nonce": '$nonce', +"chainIdHash": "'$chainIdHash'"}' + +jsontx=$(../bin/aergocli --keystore . --password bmttest \ + signtx --address $from --jsontx "$jsontx" ) + +txhash=$(../bin/aergocli committx --jsontx "$jsontx" | jq .results[0].hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +ret=$(cat receipt.json | jq .ret | sed 's/"//g') +gasUsed=$(cat receipt.json | jq .gasUsed | sed 's/"//g') + +if [ "$fork_version" -ge "4" ]; then + assert_equals "$status" "ERROR" + assert_equals "$ret" "tx not allowed recipient" + #assert_equals "$gasUsed" "117861" +else + assert_equals "$status" "ERROR" + assert_equals "$ret" "'default' is not payable" + #assert_equals "$gasUsed" "117861" +fi + + +echo "-- NORMAL type, contract with not payable 'default' function (not sending) --" + +nonce=$((nonce + 1)) + +jsontx='{ +"Account": "'$from'", +"Recipient": "'$not_payable'", +"Amount": "0", +"Type": 0, +"Nonce": '$nonce', +"chainIdHash": "'$chainIdHash'"}' + +jsontx=$(../bin/aergocli --keystore . --password bmttest \ + signtx --address $from --jsontx "$jsontx" ) + +txhash=$(../bin/aergocli committx --jsontx "$jsontx" | jq .results[0].hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +ret=$(cat receipt.json | jq .ret | sed 's/"//g') +gasUsed=$(cat receipt.json | jq .gasUsed | sed 's/"//g') + +if [ "$fork_version" -ge "4" ]; then + assert_equals "$status" "ERROR" + assert_equals "$ret" "tx not allowed recipient" + #assert_equals "$gasUsed" "117861" +else + assert_equals "$status" "SUCCESS" + assert_equals "$ret" "0" + #assert_equals "$gasUsed" "117861" +fi + + +echo "-- NORMAL type, contract with not payable 'default' function (sending) --" + +nonce=$((nonce + 1)) + +jsontx='{ +"Account": "'$from'", +"Recipient": "'$not_payable'", +"Amount": "1.23aergo", +"Type": 0, +"Nonce": '$nonce', +"chainIdHash": "'$chainIdHash'"}' + +jsontx=$(../bin/aergocli --keystore . --password bmttest \ + signtx --address $from --jsontx "$jsontx" ) + +txhash=$(../bin/aergocli committx --jsontx "$jsontx" | jq .results[0].hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +ret=$(cat receipt.json | jq .ret | sed 's/"//g') +gasUsed=$(cat receipt.json | jq .gasUsed | sed 's/"//g') + +if [ "$fork_version" -ge "4" ]; then + assert_equals "$status" "ERROR" + assert_equals "$ret" "tx not allowed recipient" + #assert_equals "$gasUsed" "117861" +else + assert_equals "$status" "ERROR" + assert_equals "$ret" "'default' is not payable" + #assert_equals "$gasUsed" "117861" +fi + + +echo "-- NORMAL type, contract with payable 'default' function (not sending) --" + +nonce=$((nonce + 1)) + +jsontx='{ +"Account": "'$from'", +"Recipient": "'$payable_default'", +"Amount": "0", +"Type": 0, +"Nonce": '$nonce', +"chainIdHash": "'$chainIdHash'"}' + +jsontx=$(../bin/aergocli --keystore . --password bmttest \ + signtx --address $from --jsontx "$jsontx" ) + +txhash=$(../bin/aergocli committx --jsontx "$jsontx" | jq .results[0].hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +ret=$(cat receipt.json | jq .ret | sed 's/"//g') +gasUsed=$(cat receipt.json | jq .gasUsed | sed 's/"//g') + +if [ "$fork_version" -ge "4" ]; then + assert_equals "$status" "ERROR" + assert_equals "$ret" "tx not allowed recipient" + #assert_equals "$gasUsed" "117861" +else + assert_equals "$status" "SUCCESS" + assert_equals "$ret" "0" + #assert_equals "$gasUsed" "117861" +fi + + +echo "-- NORMAL type, contract with payable 'default' function (sending) --" + +nonce=$((nonce + 1)) + +jsontx='{ +"Account": "'$from'", +"Recipient": "'$payable_default'", +"Amount": "1.23aergo", +"Type": 0, +"Nonce": '$nonce', +"chainIdHash": "'$chainIdHash'"}' + +jsontx=$(../bin/aergocli --keystore . --password bmttest \ + signtx --address $from --jsontx "$jsontx" ) + +txhash=$(../bin/aergocli committx --jsontx "$jsontx" | jq .results[0].hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +ret=$(cat receipt.json | jq .ret | sed 's/"//g') +gasUsed=$(cat receipt.json | jq .gasUsed | sed 's/"//g') + +if [ "$fork_version" -ge "4" ]; then + assert_equals "$status" "ERROR" + assert_equals "$ret" "tx not allowed recipient" + #assert_equals "$gasUsed" "117861" +else + assert_equals "$status" "SUCCESS" + assert_equals "$ret" "1230000000000000000" + #assert_equals "$gasUsed" "117861" +fi + + + +echo "-- CALL type, contract without 'default' function (not sending) --" + +txhash=$(../bin/aergocli --keystore . --password bmttest \ + contract call AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R \ + $no_default default | jq .hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +ret=$(cat receipt.json | jq .ret | sed 's/"//g') +gasUsed=$(cat receipt.json | jq .gasUsed | sed 's/"//g') + +assert_equals "$status" "ERROR" +assert_equals "$ret" "undefined function: default" +#assert_equals "$gasUsed" "117861" + + +echo "-- CALL type, contract without 'default' function (sending) --" + +txhash=$(../bin/aergocli --keystore . --password bmttest \ + contract call AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R \ + $no_default default --amount 1aergo | jq .hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +ret=$(cat receipt.json | jq .ret | sed 's/"//g') +gasUsed=$(cat receipt.json | jq .gasUsed | sed 's/"//g') + +assert_equals "$status" "ERROR" +assert_equals "$ret" "'default' is not payable" +#assert_equals "$gasUsed" "117861" + + +echo "-- CALL type, contract with not payable 'default' function (not sending) --" + +txhash=$(../bin/aergocli --keystore . --password bmttest \ + contract call AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R \ + $not_payable default | jq .hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +ret=$(cat receipt.json | jq .ret | sed 's/"//g') +gasUsed=$(cat receipt.json | jq .gasUsed | sed 's/"//g') + +assert_equals "$status" "SUCCESS" +assert_equals "$ret" "0" +#assert_equals "$gasUsed" "117861" + + +echo "-- CALL type, contract with not payable 'default' function (sending) --" + +txhash=$(../bin/aergocli --keystore . --password bmttest \ + contract call AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R \ + $not_payable default --amount 1aergo | jq .hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +ret=$(cat receipt.json | jq .ret | sed 's/"//g') +gasUsed=$(cat receipt.json | jq .gasUsed | sed 's/"//g') + +assert_equals "$status" "ERROR" +assert_equals "$ret" "'default' is not payable" +#assert_equals "$gasUsed" "117861" + + +echo "-- CALL type, contract with payable 'default' function --" + +txhash=$(../bin/aergocli --keystore . --password bmttest \ + contract call AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R \ + $payable_default default --amount 1aergo | jq .hash | sed 's/"//g') + +get_receipt $txhash + +status=$(cat receipt.json | jq .status | sed 's/"//g') +ret=$(cat receipt.json | jq .ret | sed 's/"//g') +gasUsed=$(cat receipt.json | jq .gasUsed | sed 's/"//g') + +assert_equals "$status" "SUCCESS" +assert_equals "$ret" "1000000000000000000" +#assert_equals "$gasUsed" "117861" diff --git a/types/account.pb.go b/types/account.pb.go index 08ea66579..44c8e5bd7 100644 --- a/types/account.pb.go +++ b/types/account.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.15.8 // source: account.proto diff --git a/types/admin.pb.go b/types/admin.pb.go index f15e7256f..b59d1579e 100644 --- a/types/admin.pb.go +++ b/types/admin.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.15.8 // source: admin.proto diff --git a/types/aergo_raft.pb.go b/types/aergo_raft.pb.go index 6d7b06911..f12b05da9 100644 --- a/types/aergo_raft.pb.go +++ b/types/aergo_raft.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.15.8 // source: aergo_raft.proto diff --git a/types/blockchain.pb.go b/types/blockchain.pb.go index 7b484c3f9..23bf90efa 100644 --- a/types/blockchain.pb.go +++ b/types/blockchain.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.15.8 // source: blockchain.proto @@ -671,6 +671,7 @@ type State struct { CodeHash []byte `protobuf:"bytes,3,opt,name=codeHash,proto3" json:"codeHash,omitempty"` StorageRoot []byte `protobuf:"bytes,4,opt,name=storageRoot,proto3" json:"storageRoot,omitempty"` SqlRecoveryPoint uint64 `protobuf:"varint,5,opt,name=sqlRecoveryPoint,proto3" json:"sqlRecoveryPoint,omitempty"` + SourceHash []byte `protobuf:"bytes,6,opt,name=sourceHash,proto3" json:"sourceHash,omitempty"` } func (x *State) Reset() { @@ -726,6 +727,13 @@ func (x *State) GetCodeHash() []byte { return nil } +func (x *State) GetSourceHash() []byte { + if x != nil { + return x.SourceHash + } + return nil +} + func (x *State) GetStorageRoot() []byte { if x != nil { return x.StorageRoot diff --git a/types/errors.go b/types/errors.go index d3ab2c969..2643f3741 100644 --- a/types/errors.go +++ b/types/errors.go @@ -38,6 +38,8 @@ var ( //ErrInvalidRecipient ErrTxInvalidRecipient = errors.New("tx invalid recipient") + ErrTxNotAllowedRecipient = errors.New("tx not allowed recipient") + ErrTxInvalidAmount = errors.New("tx invalid amount") ErrTxInvalidPrice = errors.New("tx invalid price") diff --git a/types/jsonrpc/receipt.go b/types/jsonrpc/receipt.go index ed89262c6..b57514ba6 100644 --- a/types/jsonrpc/receipt.go +++ b/types/jsonrpc/receipt.go @@ -102,32 +102,6 @@ type InOutReceipts struct { BlockNo uint64 `json:"blockNo,omitempty"` } -func ConvReceiptsPaged(msg *types.ReceiptsPaged) *InOutReceiptsPaged { - if msg == nil { - return nil - } - - rp := &InOutReceiptsPaged{} - rp.Total = msg.GetTotal() - rp.Offset = msg.GetOffset() - rp.Size = msg.GetSize() - rp.BlockNo = msg.GetBlockNo() - rp.Receipts = make([]*types.Receipt, len(msg.Get())) - for i, receipt := range msg.Get() { - rp.Receipts[i] = receipt - } - - return rp -} - -type InOutReceiptsPaged struct { - Total uint32 `json:"total,omitempty"` - Offset uint32 `json:"offset,omitempty"` - Size uint32 `json:"size,omitempty"` - Receipts []*types.Receipt `json:"receipts"` - BlockNo uint64 `json:"blockNo,omitempty"` -} - func ConvEvents(msg *types.EventList) *InOutEventList { if msg == nil { return nil diff --git a/types/metric.pb.go b/types/metric.pb.go index 6d8ea03f8..b7f7139c6 100644 --- a/types/metric.pb.go +++ b/types/metric.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.15.8 // source: metric.proto diff --git a/types/node.pb.go b/types/node.pb.go index b7516ae53..bd47652dc 100644 --- a/types/node.pb.go +++ b/types/node.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.15.8 // source: node.proto diff --git a/types/p2p.pb.go b/types/p2p.pb.go index 8373d61de..d1613e510 100644 --- a/types/p2p.pb.go +++ b/types/p2p.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.15.8 // source: p2p.proto @@ -40,11 +40,11 @@ const ( // ALREADY_EXISTS ResultStatus_ALREADY_EXISTS ResultStatus = 6 // PERMISSION_DENIED - ResultStatus_PERMISSION_DENIED ResultStatus = 7 - ResultStatus_RESOURCE_EXHAUSTED ResultStatus = 8 + ResultStatus_PERMISSION_DENIED ResultStatus = 7 + ResultStatus_RESOURCE_EXHAUSTED ResultStatus = 8 ResultStatus_FAILED_PRECONDITION ResultStatus = 9 // ABORTED - ResultStatus_ABORTED ResultStatus = 10 + ResultStatus_ABORTED ResultStatus = 10 ResultStatus_OUT_OF_RANGE ResultStatus = 11 // UNIMPLEMENTED indicates operation is not implemented or not // supported/enabled in this service. diff --git a/types/pmap.pb.go b/types/pmap.pb.go index b3155e1b8..b67701619 100644 --- a/types/pmap.pb.go +++ b/types/pmap.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.15.8 // source: pmap.proto diff --git a/types/polarrpc.pb.go b/types/polarrpc.pb.go index 0c249db0d..862a33d0f 100644 --- a/types/polarrpc.pb.go +++ b/types/polarrpc.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.15.8 // source: polarrpc.proto diff --git a/types/rpc.pb.go b/types/rpc.pb.go index 555294b2a..1d7ef4d10 100644 --- a/types/rpc.pb.go +++ b/types/rpc.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.15.8 // source: rpc.proto @@ -2657,138 +2657,6 @@ func (x *EnterpriseConfig) GetValues() []string { return nil } -type ReceiptsParams struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Hashornumber []byte `protobuf:"bytes,1,opt,name=hashornumber,proto3" json:"hashornumber,omitempty"` - Paging *PageParams `protobuf:"bytes,2,opt,name=paging,proto3" json:"paging,omitempty"` -} - -func (x *ReceiptsParams) Reset() { - *x = ReceiptsParams{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ReceiptsParams) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ReceiptsParams) ProtoMessage() {} - -func (x *ReceiptsParams) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[16] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -func (*ReceiptsParams) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{16} -} - -func (x *ReceiptsParams) GetHashornumber() []byte { - if x != nil { - return x.Hashornumber - } - return nil -} - -func (x *ReceiptsParams) GetPaging() *PageParams { - if x != nil { - return x.Paging - } - return nil -} - -type ReceiptsPaged struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Total uint32 `protobuf:"varint,1,opt,name=total,proto3" json:"total,omitempty"` - Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` - Size uint32 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"` - Receipts []*Receipt `protobuf:"bytes,4,opt,name=body,proto3" json:"receipts,omitempty"` - BlockNo BlockNo `protobuf:"bytes,4,opt,name=body,proto3" json:"blockNo,omitempty"` -} - -func (x *ReceiptsPaged) Reset() { - *x = ReceiptsPaged{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ReceiptsPaged) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ReceiptsPaged) ProtoMessage() {} - -func (x *ReceiptsPaged) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[15] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -func (*ReceiptsPaged) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{15} -} - -func (x *ReceiptsPaged) GetTotal() uint32 { - if x != nil { - return x.Total - } - return 0 -} - -func (x *ReceiptsPaged) GetOffset() uint32 { - if x != nil { - return x.Offset - } - return 0 -} - -func (x *ReceiptsPaged) GetSize() uint32 { - if x != nil { - return x.Size - } - return 0 -} - -func (x *ReceiptsPaged) Get() []*Receipt { - if x != nil { - return x.Receipts - } - return nil -} - -func (x *ReceiptsPaged) GetBlockNo() uint64 { - if x != nil { - return x.BlockNo - } - return 0 -} - var File_rpc_proto protoreflect.FileDescriptor var file_rpc_proto_rawDesc = []byte{ diff --git a/types/token_unit.go b/types/token_unit.go index 1bf2caf35..18ac87547 100644 --- a/types/token_unit.go +++ b/types/token_unit.go @@ -17,3 +17,7 @@ func NewAmount(amount uint64, unit TokenUnit) *big.Int { func NewZeroAmount() *big.Int { return new(big.Int) } + +func IsZeroAmount(n *big.Int) bool { + return n.Sign() == 0 +}