diff --git a/Changelog.md b/Changelog.md index f46419c..5605422 100644 --- a/Changelog.md +++ b/Changelog.md @@ -24,12 +24,17 @@ - Few more ideas implemented and will be tested for review in upcoming technology preview. - + + ###3.4 - DAG/MINIDAG with blocks flowing every second - Mining Decentralization.No more mining pools, daily 100000 reward blocks, no need for pools and thus no attacks - Erasure coded blocks, lower bandwidth requirements, very low propagation time. Tested with upto 20 MB blocks. - DERO Simulator for faster Development cycle +- Gas Support implemented ( both storage gas/compute gas) +- Implemented gas estimation +- DVM simulator to test all edge cases for SC dev, see dvm/simulator_test.go to see it in action for lotter SC. ###3.3 diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 768bdcf..e99eaa3 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -45,6 +45,7 @@ import "github.com/deroproject/derohe/cryptography/crypto" import "github.com/deroproject/derohe/errormsg" import "github.com/deroproject/derohe/metrics" +import "github.com/deroproject/derohe/dvm" import "github.com/deroproject/derohe/block" import "github.com/deroproject/derohe/globals" import "github.com/deroproject/derohe/transaction" @@ -901,12 +902,12 @@ func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err erro // at this point, we must commit all the SCs, so entire tree hash is interlinked for scid, v := range sc_change_cache { - meta_bytes, err := sc_meta.Get(SC_Meta_Key(scid)) + meta_bytes, err := sc_meta.Get(dvm.SC_Meta_Key(scid)) if err != nil { panic(err) } - var meta SC_META_DATA // the meta contains metadata about SC + var meta dvm.SC_META_DATA // the meta contains metadata about SC if err := meta.UnmarshalBinary(meta_bytes); err != nil { panic(err) } @@ -915,7 +916,7 @@ func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err erro panic(err) } - sc_meta.Put(SC_Meta_Key(scid), meta.MarshalBinary()) + sc_meta.Put(dvm.SC_Meta_Key(scid), meta.MarshalBinary()) data_trees = append(data_trees, v) /*fmt.Printf("will commit tree name %x \n", v.GetName()) @@ -1163,6 +1164,14 @@ func (chain *Blockchain) Add_TX_To_Pool(tx *transaction.Transaction) error { return fmt.Errorf("TX %s rejected Already mined in some block", txhash) } + toporecord, err := chain.Store.Topo_store.Read(int64(tx.Height)) + if err != nil { + return fmt.Errorf("TX %s rejected height(%d) reference not found", txhash, tx.Height) + } + if toporecord.BLOCK_ID != tx.BLID { + return fmt.Errorf("TX %s rejected block (%s) reference not found", txhash, tx.BLID) + } + hf_version := chain.Get_Current_Version_at_Height(int64(chain_height)) // if TX is too big, then it cannot be mined due to fixed block size, reject such TXs here diff --git a/blockchain/hardcoded_contracts.go b/blockchain/hardcoded_contracts.go index 9321f0e..e0d4e75 100644 --- a/blockchain/hardcoded_contracts.go +++ b/blockchain/hardcoded_contracts.go @@ -74,31 +74,31 @@ func (chain *Blockchain) install_hardcoded_contracts(cache map[crypto.Hash]*grav // hard coded contracts generally do not do any initialization func (chain *Blockchain) install_hardcoded_sc(cache map[crypto.Hash]*graviton.Tree, ss *graviton.Snapshot, balance_tree *graviton.Tree, sc_tree *graviton.Tree, source string, scid crypto.Hash) (err error) { - w_sc_tree := &Tree_Wrapper{tree: sc_tree, entries: map[string][]byte{}} - var w_sc_data_tree *Tree_Wrapper + w_sc_tree := &dvm.Tree_Wrapper{Tree: sc_tree, Entries: map[string][]byte{}} + var w_sc_data_tree *dvm.Tree_Wrapper - meta := SC_META_DATA{} - w_sc_data_tree = wrapped_tree(cache, ss, scid) + meta := dvm.SC_META_DATA{} + w_sc_data_tree = dvm.Wrapped_tree(cache, ss, scid) // install SC, should we check for sanity now, why or why not - w_sc_data_tree.Put(SC_Code_Key(scid), dvm.Variable{Type: dvm.String, ValueString: source}.MarshalBinaryPanic()) - w_sc_tree.Put(SC_Meta_Key(scid), meta.MarshalBinary()) + w_sc_data_tree.Put(dvm.SC_Code_Key(scid), dvm.Variable{Type: dvm.String, ValueString: source}.MarshalBinaryPanic()) + w_sc_tree.Put(dvm.SC_Meta_Key(scid), meta.MarshalBinary()) // we must commit all the changes // anything below should never give error if _, ok := cache[scid]; !ok { - cache[scid] = w_sc_data_tree.tree + cache[scid] = w_sc_data_tree.Tree } - for k, v := range w_sc_data_tree.entries { // commit entire data to tree - if err = w_sc_data_tree.tree.Put([]byte(k), v); err != nil { + for k, v := range w_sc_data_tree.Entries { // commit entire data to tree + if err = w_sc_data_tree.Tree.Put([]byte(k), v); err != nil { return } } - for k, v := range w_sc_tree.entries { - if err = w_sc_tree.tree.Put([]byte(k), v); err != nil { + for k, v := range w_sc_tree.Entries { + if err = w_sc_tree.Tree.Put([]byte(k), v); err != nil { return } } diff --git a/blockchain/mempool/mempool.go b/blockchain/mempool/mempool.go index e5a2325..15a21f7 100644 --- a/blockchain/mempool/mempool.go +++ b/blockchain/mempool/mempool.go @@ -103,7 +103,7 @@ func (pool *Mempool) HouseKeeping(height uint64) { pool.txs.Range(func(k, value interface{}) bool { txhash := k.(crypto.Hash) v := value.(*mempool_object) - if height >= (v.Tx.Height) { // if we have moved 10 heights, chances of reorg are almost nil + if height >= (v.Tx.Height) { // remove all txs delete_list = append(delete_list, txhash) } return true diff --git a/blockchain/transaction_execute.go b/blockchain/transaction_execute.go index b9f065e..abaf6c6 100644 --- a/blockchain/transaction_execute.go +++ b/blockchain/transaction_execute.go @@ -24,7 +24,6 @@ import "strings" import "strconv" import "runtime/debug" import "encoding/hex" -import "encoding/binary" import "math/big" import "golang.org/x/xerrors" @@ -243,38 +242,6 @@ func (chain *Blockchain) process_transaction(changed map[crypto.Hash]*graviton.T } } -type Tree_Wrapper struct { - tree *graviton.Tree - entries map[string][]byte - transfere []dvm.TransferExternal -} - -func (t *Tree_Wrapper) Get(key []byte) ([]byte, error) { - if value, ok := t.entries[string(key)]; ok { - return value, nil - } else { - return t.tree.Get(key) - } -} - -func (t *Tree_Wrapper) Put(key []byte, value []byte) error { - t.entries[string(key)] = append([]byte{}, value...) - return nil -} - -// checks cache and returns a wrapped tree if possible -func wrapped_tree(cache map[crypto.Hash]*graviton.Tree, ss *graviton.Snapshot, id crypto.Hash) *Tree_Wrapper { - if cached_tree, ok := cache[id]; ok { // tree is in cache return it - return &Tree_Wrapper{tree: cached_tree, entries: map[string][]byte{}} - } - - if tree, err := ss.GetTree(string(id[:])); err != nil { - panic(err) - } else { - return &Tree_Wrapper{tree: tree, entries: map[string][]byte{}} - } -} - // does additional processing for SC // all processing occurs in wrapped trees, if any error occurs we dicard all trees func (chain *Blockchain) process_transaction_sc(cache map[crypto.Hash]*graviton.Tree, ss *graviton.Snapshot, bl_height, bl_topoheight, bl_timestamp uint64, blid crypto.Hash, tx transaction.Transaction, balance_tree *graviton.Tree, sc_tree *graviton.Tree) (gas uint64, err error) { @@ -285,11 +252,13 @@ func (chain *Blockchain) process_transaction_sc(cache map[crypto.Hash]*graviton. gas = tx.Fees() - w_balance_tree := &Tree_Wrapper{tree: balance_tree, entries: map[string][]byte{}} - w_sc_tree := &Tree_Wrapper{tree: sc_tree, entries: map[string][]byte{}} + var gascompute, gasstorage uint64 - _ = w_balance_tree - var w_sc_data_tree *Tree_Wrapper + _ = gascompute + _ = gasstorage + + w_sc_tree := &dvm.Tree_Wrapper{Tree: sc_tree, Entries: map[string][]byte{}} + var w_sc_data_tree *dvm.Tree_Wrapper txhash := tx.GetHash() scid := txhash @@ -302,10 +271,22 @@ func (chain *Blockchain) process_transaction_sc(cache map[crypto.Hash]*graviton. }() if !tx.SCDATA.Has(rpc.SCACTION, rpc.DataUint64) { // tx doesn't have sc action - //err = fmt.Errorf("no scid provided") return tx.Fees(), nil } + incoming_value := map[crypto.Hash]uint64{} + for _, payload := range tx.Payloads { + incoming_value[payload.SCID] = payload.BurnValue + } + + chain.Expand_Transaction_NonCoinbase(&tx) + + signer, err := Extract_signer(&tx) + if err != nil { // allow anonymous SC transactions with condition that SC will not call Signer + // this allows anonymous voting and numerous other applications + // otherwise SC receives signer as all zeroes + } + action_code := rpc.SC_ACTION(tx.SCDATA.Value(rpc.SCACTION, rpc.DataUint64).(uint64)) switch action_code { @@ -329,21 +310,28 @@ func (chain *Blockchain) process_transaction_sc(cache map[crypto.Hash]*graviton. break } - meta := SC_META_DATA{} + meta := dvm.SC_META_DATA{} if _, ok := sc.Functions["InitializePrivate"]; ok { meta.Type = 1 } - w_sc_data_tree = wrapped_tree(cache, ss, scid) + w_sc_data_tree = dvm.Wrapped_tree(cache, ss, scid) // install SC, should we check for sanity now, why or why not - w_sc_data_tree.Put(SC_Code_Key(scid), dvm.Variable{Type: dvm.String, ValueString: sc_code}.MarshalBinaryPanic()) - w_sc_tree.Put(SC_Meta_Key(scid), meta.MarshalBinary()) + w_sc_data_tree.Put(dvm.SC_Code_Key(scid), dvm.Variable{Type: dvm.String, ValueString: sc_code}.MarshalBinaryPanic()) + w_sc_tree.Put(dvm.SC_Meta_Key(scid), meta.MarshalBinary()) + entrypoint := "Initialize" if meta.Type == 1 { // if its a a private SC - gas, err = chain.execute_sc_function(w_sc_tree, w_sc_data_tree, scid, bl_height, bl_topoheight, bl_timestamp, blid, tx, "InitializePrivate", 1) + entrypoint = "InitializePrivate" + } + + balance, sc_parsed, found := dvm.ReadSC(w_sc_tree, w_sc_data_tree, scid) + if found { + gascompute, gasstorage, err = dvm.Execute_sc_function(w_sc_tree, w_sc_data_tree, scid, bl_height, bl_topoheight, bl_timestamp, blid, txhash, sc_parsed, entrypoint, 1, balance, signer, incoming_value, tx.SCDATA, tx.Fees(), chain.simulator) } else { - gas, err = chain.execute_sc_function(w_sc_tree, w_sc_data_tree, scid, bl_height, bl_topoheight, bl_timestamp, blid, tx, "Initialize", 1) + logger.V(1).Error(nil, "SC not found", "scid", scid) + err = fmt.Errorf("SC not found %s", scid) } if err != nil { @@ -364,17 +352,23 @@ func (chain *Blockchain) process_transaction_sc(cache map[crypto.Hash]*graviton. scid = tx.SCDATA.Value(rpc.SCID, rpc.DataHash).(crypto.Hash) - if _, err = w_sc_tree.Get(SC_Meta_Key(scid)); err != nil { + if _, err = w_sc_tree.Get(dvm.SC_Meta_Key(scid)); err != nil { err = fmt.Errorf("scid %s not installed", scid) return } - w_sc_data_tree = wrapped_tree(cache, ss, scid) + w_sc_data_tree = dvm.Wrapped_tree(cache, ss, scid) entrypoint := tx.SCDATA.Value("entrypoint", rpc.DataString).(string) //fmt.Printf("We must call the SC %s function\n", entrypoint) - gas, err = chain.execute_sc_function(w_sc_tree, w_sc_data_tree, scid, bl_height, bl_topoheight, bl_timestamp, blid, tx, entrypoint, 1) + balance, sc_parsed, found := dvm.ReadSC(w_sc_tree, w_sc_data_tree, scid) + if found { + gascompute, gasstorage, err = dvm.Execute_sc_function(w_sc_tree, w_sc_data_tree, scid, bl_height, bl_topoheight, bl_timestamp, blid, txhash, sc_parsed, entrypoint, 1, balance, signer, incoming_value, tx.SCDATA, tx.Fees(), chain.simulator) + } else { + logger.V(1).Error(nil, "SC not found", "scid", scid) + err = fmt.Errorf("SC not found %s", scid) + } default: // unknown what to do err = fmt.Errorf("unknown action what to do scid %x", scid) @@ -384,48 +378,7 @@ func (chain *Blockchain) process_transaction_sc(cache map[crypto.Hash]*graviton. // we must commit all the changes // check whether we are not overflowing/underflowing, means SC is not over sending if err == nil { - total_per_asset := map[crypto.Hash]uint64{} - for _, transfer := range w_sc_data_tree.transfere { // do external tranfer - if transfer.Amount == 0 { - continue - } - - // an SCID can generate it's token infinitely - if transfer.Asset != scid && total_per_asset[transfer.Asset]+transfer.Amount <= total_per_asset[transfer.Asset] { - err = fmt.Errorf("Balance calculation overflow") - break - } else { - total_per_asset[transfer.Asset] = total_per_asset[transfer.Asset] + transfer.Amount - } - } - - if err == nil { - for asset, value := range total_per_asset { - stored_value, _ := chain.LoadSCAssetValue(w_sc_data_tree, scid, asset) - // an SCID can generate it's token infinitely - if asset != scid && stored_value-value > stored_value { - err = fmt.Errorf("Balance calculation underflow stored_value %d transferring %d\n", stored_value, value) - break - } - - var new_value [8]byte - binary.BigEndian.PutUint64(new_value[:], stored_value-value) - chain.StoreSCValue(w_sc_data_tree, scid, asset[:], new_value[:]) - } - } - - //also check whether all destinations are registered - if err == nil { - for _, transfer := range w_sc_data_tree.transfere { - if _, err = balance_tree.Get([]byte(transfer.Address)); err == nil || xerrors.Is(err, graviton.ErrNotFound) { - // everything is okay - } else { - err = fmt.Errorf("account is unregistered") - logger.V(1).Error(err, "account is unregistered", "txhash", txhash, "scid", scid, "address", transfer.Address) - break - } - } - } + err = dvm.SanityCheckExternalTransfers(w_sc_data_tree, balance_tree, scid) } if err != nil { // error occured, give everything to SC, since we may not have information to send them back @@ -433,111 +386,15 @@ func (chain *Blockchain) process_transaction_sc(cache map[crypto.Hash]*graviton. logger.Error(err, "error executing sc", "txid", txhash) } - for _, payload := range tx.Payloads { - var new_value [8]byte - - w_sc_data_tree = wrapped_tree(cache, ss, scid) // get a new tree, discarding everything - - stored_value, _ := chain.LoadSCAssetValue(w_sc_data_tree, scid, payload.SCID) - binary.BigEndian.PutUint64(new_value[:], stored_value+payload.BurnValue) - chain.StoreSCValue(w_sc_data_tree, scid, payload.SCID[:], new_value[:]) - - for k, v := range w_sc_data_tree.entries { // commit incoming balances to tree - if err = w_sc_data_tree.tree.Put([]byte(k), v); err != nil { - return - } - } - - //for k, v := range w_sc_tree.entries { - // if err = w_sc_tree.tree.Put([]byte(k), v); err != nil { - // return - // } - //} - + if signer, err1 := Extract_signer(&tx); err1 == nil { // if we can identify sender, return funds to him + dvm.ErrorRevert(ss, cache, balance_tree, signer, scid, incoming_value) + } else { // we could not extract signer, give burned funds to SC + dvm.ErrorRevert(ss, cache, balance_tree, signer, scid, incoming_value) } return } - - // anything below should never give error - if _, ok := cache[scid]; !ok { - cache[scid] = w_sc_data_tree.tree - } - - for k, v := range w_sc_data_tree.entries { // commit entire data to tree - if _, ok := globals.Arguments["--debug"]; ok && globals.Arguments["--debug"] != nil && chain.simulator { - logger.V(1).Info("Writing", "txid", txhash, "scid", scid, "key", fmt.Sprintf("%x", k), "value", fmt.Sprintf("%x", v)) - } - if len(v) == 0 { - if err = w_sc_data_tree.tree.Delete([]byte(k)); err != nil { - return - } - } else { - if err = w_sc_data_tree.tree.Put([]byte(k), v); err != nil { - return - } - } - } - - for k, v := range w_sc_tree.entries { - if err = w_sc_tree.tree.Put([]byte(k), v); err != nil { - return - } - } - - for i, transfer := range w_sc_data_tree.transfere { // do external tranfer - if transfer.Amount == 0 { - continue - } - //fmt.Printf("%d sending to external %s %x\n", i,transfer.Asset,transfer.Address) - var zeroscid crypto.Hash - - var curbtree *graviton.Tree - switch transfer.Asset { - case zeroscid: // main dero balance, handle it - curbtree = balance_tree - case scid: // this scid balance, handle it - curbtree = cache[scid] - default: // any other asset scid - var ok bool - if curbtree, ok = cache[transfer.Asset]; !ok { - if curbtree, err = ss.GetTree(string(transfer.Asset[:])); err != nil { - panic(err) - } - cache[transfer.Asset] = curbtree - } - } - - if curbtree == nil { - panic("tree cannot be nil at this point in time") - } - - addr_bytes := []byte(transfer.Address) - if _, err = balance_tree.Get(addr_bytes); err != nil { // first check whether address is registered - err = fmt.Errorf("sending to non registered account acc %x err %s", addr_bytes, err) // this can only occur, if account no registered or dis corruption - panic(err) - } - - var balance_serialized []byte - balance_serialized, err = curbtree.Get(addr_bytes) - if err != nil && xerrors.Is(err, graviton.ErrNotFound) { // if the address is not found, lookup in main tree - var p bn256.G1 - if err = p.DecodeCompressed(addr_bytes[:]); err != nil { - panic(fmt.Errorf("key %x could not be decompressed", addr_bytes)) - } - - balance := crypto.ConstructElGamal(&p, crypto.ElGamal_BASE_G) // init zero balance - nb := crypto.NonceBalance{NonceHeight: 0, Balance: balance} - balance_serialized = nb.Serialize() - } else if err != nil { - fmt.Printf("%s %d could not transfer %d %+v\n", scid, i, transfer.Amount, addr_bytes) - panic(err) // only disk corruption can reach here - } - - nb := new(crypto.NonceBalance).Deserialize(balance_serialized) - nb.Balance = nb.Balance.Plus(new(big.Int).SetUint64(transfer.Amount)) // add transfer to users balance homomorphically - curbtree.Put(addr_bytes, nb.Serialize()) // reserialize and store - } + dvm.ProcessExternal(ss, cache, balance_tree, signer, scid, w_sc_data_tree, w_sc_tree) //c := w_sc_data_tree.tree.Cursor() //for k, v, err := c.First(); err == nil; k, v, err = c.Next() { @@ -553,7 +410,7 @@ func (chain *Blockchain) process_transaction_sc(cache map[crypto.Hash]*graviton. // func extract signer from a tx, if possible // extract signer is only possible if ring size is 2 -func extract_signer(tx *transaction.Transaction) (signer [33]byte, err error) { +func Extract_signer(tx *transaction.Transaction) (signer [33]byte, err error) { for t := range tx.Payloads { if uint64(len(tx.Payloads[t].Statement.Publickeylist_compressed)) != tx.Payloads[t].Statement.RingSize { panic("tx is not expanded") diff --git a/cmd/dero-wallet-cli/easymenu_post_open.go b/cmd/dero-wallet-cli/easymenu_post_open.go index 02c4746..89d672e 100644 --- a/cmd/dero-wallet-cli/easymenu_post_open.go +++ b/cmd/dero-wallet-cli/easymenu_post_open.go @@ -177,7 +177,7 @@ func handle_easymenu_post_open_command(l *readline.Instance, line string) (proce } if ConfirmYesNoDefaultNo(l, "Confirm Transaction (y/N)") { - tx, err := wallet.TransferPayload0([]rpc.Transfer{rpc.Transfer{SCID: scid, Amount: amount_to_transfer, Destination: a.String()}}, 0, false, rpc.Arguments{}, false) // empty SCDATA + tx, err := wallet.TransferPayload0([]rpc.Transfer{rpc.Transfer{SCID: scid, Amount: amount_to_transfer, Destination: a.String()}}, 0, false, rpc.Arguments{}, 0, false) // empty SCDATA if err != nil { logger.Error(err, "Error while building Transaction") @@ -332,7 +332,7 @@ func handle_easymenu_post_open_command(l *readline.Instance, line string) (proce //src_port := uint64(0xffffffffffffffff) - tx, err := wallet.TransferPayload0([]rpc.Transfer{rpc.Transfer{Amount: amount_to_transfer, Destination: a.String(), Payload_RPC: arguments}}, 0, false, rpc.Arguments{}, false) // empty SCDATA + tx, err := wallet.TransferPayload0([]rpc.Transfer{rpc.Transfer{Amount: amount_to_transfer, Destination: a.String(), Payload_RPC: arguments}}, 0, false, rpc.Arguments{}, 0, false) // empty SCDATA if err != nil { logger.Error(err, "Error while building Transaction") diff --git a/cmd/dero-wallet-cli/prompt.go b/cmd/dero-wallet-cli/prompt.go index 2efcd74..6a22fd8 100644 --- a/cmd/dero-wallet-cli/prompt.go +++ b/cmd/dero-wallet-cli/prompt.go @@ -216,7 +216,7 @@ func handle_prompt_command(l *readline.Instance, line string) { //uid, err := wallet.PoolTransferWithBurn(addr, send_amount, burn_amount, data, rpc.Arguments{}) - tx, err := wallet.TransferPayload0([]rpc.Transfer{rpc.Transfer{Amount: send_amount, Burn: burn_amount, Destination: addr}}, 0, false, rpc.Arguments{}, false) // empty SCDATA + tx, err := wallet.TransferPayload0([]rpc.Transfer{rpc.Transfer{Amount: send_amount, Burn: burn_amount, Destination: addr}}, 0, false, rpc.Arguments{}, 0, false) // empty SCDATA if err != nil { logger.Error(err, "Error while building Transaction") diff --git a/cmd/derod/rpc/rpc_dero_estimategas.go b/cmd/derod/rpc/rpc_dero_estimategas.go new file mode 100644 index 0000000..cf522ec --- /dev/null +++ b/cmd/derod/rpc/rpc_dero_estimategas.go @@ -0,0 +1,93 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package rpc + +import "fmt" +import "context" +import "strings" + +//import "encoding/binary" +import "encoding/base64" +import "runtime/debug" + +import "github.com/deroproject/derohe/cryptography/crypto" + +//import "github.com/deroproject/derohe/config" +import "github.com/deroproject/derohe/rpc" + +import "github.com/deroproject/derohe/dvm" + +//import "github.com/deroproject/derohe/transaction" +//import "github.com/deroproject/derohe/blockchain" + +import "github.com/deroproject/graviton" + +func GetGasEstimate(ctx context.Context, p rpc.GasEstimate_Params) (result rpc.GasEstimate_Result, err error) { + defer func() { // safety so if anything wrong happens, we return error + if r := recover(); r != nil { + err = fmt.Errorf("panic occured. stack trace r %s %s", r, debug.Stack()) + } + }() + + if len(p.SC_Code) >= 1 && !strings.Contains(strings.ToLower(p.SC_Code), "initialize") { // decode SC from base64 if possible, since json hash limitations + if sc, err := base64.StdEncoding.DecodeString(p.SC_Code); err == nil { + p.SC_Code = string(sc) + } + } + + var signer *rpc.Address + + if len(p.Signer) > 0 { + if signer, err = rpc.NewAddress(p.Signer); err != nil { + return + } + } + + incoming_values := map[crypto.Hash]uint64{} + for _, t := range p.Transfers { + if t.Burn > 0 { + incoming_values[t.SCID] += t.Burn + } + } + + toporecord, err := chain.Store.Topo_store.Read(chain.Load_TOPO_HEIGHT()) + // we must now fill in compressed ring members + if err == nil { + var ss *graviton.Snapshot + ss, err = chain.Store.Balance_store.LoadSnapshot(toporecord.State_Version) + if err == nil { + s := dvm.SimulatorInitialize(ss) + if len(p.SC_Code) >= 1 { // we need to install the SC + if _, result.GasCompute, result.GasStorage, err = s.SCInstall(p.SC_Code, incoming_values, p.SC_RPC, signer, 0); err != nil { + return + } + } else { // we need to estimate gas for already installed contract + if result.GasCompute, result.GasStorage, err = s.RunSC(incoming_values, p.SC_RPC, signer, 0); err != nil { + return + } + } + } + } + + //fmt.Printf("p %+v\n", p) + + result.Status = "OK" + err = nil + + //logger.Debugf("result %+v\n", result); + return +} diff --git a/cmd/derod/rpc/rpc_dero_getrandomaddress.go b/cmd/derod/rpc/rpc_dero_getrandomaddress.go index e2e3aab..2ff3643 100644 --- a/cmd/derod/rpc/rpc_dero_getrandomaddress.go +++ b/cmd/derod/rpc/rpc_dero_getrandomaddress.go @@ -17,6 +17,7 @@ package rpc import "fmt" +import "bytes" import "context" import "runtime/debug" import "github.com/deroproject/derohe/config" @@ -26,6 +27,7 @@ import "github.com/deroproject/derohe/rpc" //import "github.com/deroproject/derohe/blockchain" +// only give random members who have not been used in last 5 blocks func GetRandomAddress(ctx context.Context, p rpc.GetRandomAddress_Params) (result rpc.GetRandomAddress_Result, err error) { defer func() { // safety so if anything wrong happens, we return error if r := recover(); r != nil { @@ -33,9 +35,10 @@ func GetRandomAddress(ctx context.Context, p rpc.GetRandomAddress_Params) (resul } }() topoheight := chain.Load_TOPO_HEIGHT() + old_topoheight := topoheight - if topoheight > 100 { - topoheight -= 5 + if old_topoheight > 100 { + old_topoheight -= 5 } var cursor_list []string @@ -43,14 +46,22 @@ func GetRandomAddress(ctx context.Context, p rpc.GetRandomAddress_Params) (resul { toporecord, err := chain.Store.Topo_store.Read(topoheight) - if err != nil { panic(err) } + toporecord_old, err := chain.Store.Topo_store.Read(old_topoheight) + if err != nil { + panic(err) + } + ss, err := chain.Store.Balance_store.LoadSnapshot(toporecord.State_Version) if err != nil { panic(err) } + ss_old, err := chain.Store.Balance_store.LoadSnapshot(toporecord_old.State_Version) + if err != nil { + panic(err) + } treename := config.BALANCE_TREE if !p.SCID.IsZero() { @@ -61,15 +72,27 @@ func GetRandomAddress(ctx context.Context, p rpc.GetRandomAddress_Params) (resul if err != nil { panic(err) } + balance_tree_old, err := ss_old.GetTree(treename) + if err != nil { + panic(err) + } account_map := map[string]bool{} for i := 0; i < 100; i++ { - k, _, err := balance_tree.Random() + k, v, err := balance_tree.Random() if err != nil { continue } + v_old, err := balance_tree_old.Get(k) + if err != nil { + continue + } + + if bytes.Compare(v, v_old) != 0 { + continue + } var acckey crypto.Point if err := acckey.DecodeCompressed(k[:]); err != nil { diff --git a/cmd/derod/rpc/rpc_dero_getsc.go b/cmd/derod/rpc/rpc_dero_getsc.go index cea95a4..37c6a3a 100644 --- a/cmd/derod/rpc/rpc_dero_getsc.go +++ b/cmd/derod/rpc/rpc_dero_getsc.go @@ -30,7 +30,6 @@ import "github.com/deroproject/derohe/rpc" import "github.com/deroproject/derohe/dvm" //import "github.com/deroproject/derohe/transaction" -import "github.com/deroproject/derohe/blockchain" import "github.com/deroproject/graviton" @@ -87,7 +86,7 @@ func GetSC(ctx context.Context, p rpc.GetSC_Params) (result rpc.GetSC_Result, er if p.Code { // give SC code var code_bytes []byte var v dvm.Variable - if code_bytes, err = sc_data_tree.Get(blockchain.SC_Code_Key(scid)); err == nil { + if code_bytes, err = sc_data_tree.Get(dvm.SC_Code_Key(scid)); err == nil { if err = v.UnmarshalBinary(code_bytes); err != nil { result.Code = "Unmarshal error" } else { diff --git a/cmd/derod/rpc/rpc_dero_gettransactions.go b/cmd/derod/rpc/rpc_dero_gettransactions.go index e83227c..19ab0ed 100644 --- a/cmd/derod/rpc/rpc_dero_gettransactions.go +++ b/cmd/derod/rpc/rpc_dero_gettransactions.go @@ -24,8 +24,10 @@ import "runtime/debug" //import "github.com/romana/rlog" import "github.com/deroproject/derohe/cryptography/crypto" +import "github.com/deroproject/derohe/cryptography/bn256" import "github.com/deroproject/derohe/config" import "github.com/deroproject/derohe/rpc" +import "github.com/deroproject/derohe/dvm" import "github.com/deroproject/derohe/globals" import "github.com/deroproject/derohe/transaction" import "github.com/deroproject/derohe/blockchain" @@ -115,7 +117,7 @@ func GetTransaction(ctx context.Context, p rpc.GetTransaction_Params) (result rp if sc_data_tree, err := ss.GetTree(string(scid[:])); err == nil { var code_bytes []byte - if code_bytes, err = sc_data_tree.Get(blockchain.SC_Code_Key(scid)); err == nil { + if code_bytes, err = sc_data_tree.Get(dvm.SC_Code_Key(scid)); err == nil { related.Code = string(code_bytes) } @@ -149,6 +151,15 @@ func GetTransaction(ctx context.Context, p rpc.GetTransaction_Params) (result rp related.Ring = append(related.Ring, ring) } + if signer, err1 := blockchain.Extract_signer(&tx); err1 == nil { + var p bn256.G1 + if err = p.DecodeCompressed(signer[:]); err == nil { + s := rpc.NewAddressFromKeys((*crypto.Point)(&p)) + s.Mainnet = globals.Config.Name == config.Mainnet.Name + related.Signer = s.String() + } + } + } } } diff --git a/cmd/derod/rpc/websocket_server.go b/cmd/derod/rpc/websocket_server.go index f32c285..7c25e03 100644 --- a/cmd/derod/rpc/websocket_server.go +++ b/cmd/derod/rpc/websocket_server.go @@ -115,10 +115,13 @@ func Notify_MiniBlock_Addition() { chain.RPC_NotifyNewMiniBlock.L.Lock() chain.RPC_NotifyNewMiniBlock.Wait() chain.RPC_NotifyNewMiniBlock.L.Unlock() - go func() { - defer globals.Recover(2) - SendJob() - }() + + if globals.Arguments["--simulator"] == nil || (globals.Arguments["--simulator"] != nil && globals.Arguments["--simulator"].(bool) == false) { + go func() { + defer globals.Recover(2) + SendJob() + }() + } } } @@ -310,6 +313,7 @@ var historical_apis = handler.Map{"getinfo": handler.New(GetInfo), "getblocktemplate": handler.New(GetBlockTemplate), "getencryptedbalance": handler.New(GetEncryptedBalance), "getsc": handler.New(GetSC), + "getgasestimate": handler.New(GetGasEstimate), "nametoaddress": handler.New(NameToAddress)} var servicemux = handler.ServiceMap{ @@ -331,6 +335,7 @@ var servicemux = handler.ServiceMap{ "GetBlockTemplate": handler.New(GetBlockTemplate), "GetEncryptedBalance": handler.New(GetEncryptedBalance), "GetSC": handler.New(GetSC), + "GetGasEstimate": handler.New(GetGasEstimate), "NameToAddress": handler.New(NameToAddress), }, "DAEMON": handler.Map{ diff --git a/cmd/explorer/explorerlib/explorerlib.go b/cmd/explorer/explorerlib/explorerlib.go index e111199..5f26922 100644 --- a/cmd/explorer/explorerlib/explorerlib.go +++ b/cmd/explorer/explorerlib/explorerlib.go @@ -474,6 +474,7 @@ func load_tx_info_from_tx(info *txinfo, tx *transaction.Transaction) (err error) if tx.TransactionType == transaction.SC_TX { info.SC_Args = tx.SCDATA + } // if outputs cannot be located, do not panic @@ -556,6 +557,10 @@ func load_tx_from_rpc(info *txinfo, txhash string) (err error) { info.Ring = tx_result.Txs[0].Ring + if tx.TransactionType == transaction.SC_TX { + info.SC_Signer = "Unknown" + } + if tx.TransactionType == transaction.NORMAL || tx.TransactionType == transaction.BURN_TX || tx.TransactionType == transaction.SC_TX { for t := range tx.Payloads { @@ -570,6 +575,7 @@ func load_tx_from_rpc(info *txinfo, txhash string) (err error) { a.Ring_size = len(tx_result.Txs[0].Ring[t]) a.Ring = tx_result.Txs[0].Ring[t] + info.SC_Signer = tx_result.Txs[0].Signer info.Assets = append(info.Assets, a) diff --git a/cmd/explorer/explorerlib/templates/tx.tmpl b/cmd/explorer/explorerlib/templates/tx.tmpl index 4ecf10f..ff13532 100644 --- a/cmd/explorer/explorerlib/templates/tx.tmpl +++ b/cmd/explorer/explorerlib/templates/tx.tmpl @@ -103,7 +103,12 @@ {{range $ii, $ee := .info.Assets}} {{if eq $ee.SCID "0000000000000000000000000000000000000000000000000000000000000000" }} -
DERO : {{$ee.Ring_size}} inputs/outputs (RING size) Fees {{$ee.Fees}} + + + +
DERO : {{$ee.Ring_size}} inputs/outputs (RING size) Fees {{$ee.Fees}} + + {{if eq $.info.TransactionType "SC"}} Deposited to SC {{$ee.Burn}} @@ -112,6 +117,7 @@ {{end}}
+ {{if eq $.info.TransactionType "SC"}}
Sender : {{ $.info.SC_Signer }}
{{end}} {{else}}
Token: {{$ee.SCID}} {{$ee.Ring_size}} inputs/outputs (RING size) Fees {{$ee.Fees}} {{if eq $.info.TransactionType "SC"}} diff --git a/cmd/simulator/blockchain_sim_test.go b/cmd/simulator/blockchain_sim_test.go index 32e5426..9f7b8d3 100644 --- a/cmd/simulator/blockchain_sim_test.go +++ b/cmd/simulator/blockchain_sim_test.go @@ -211,7 +211,7 @@ func Test_Creation_TX(t *testing.T) { // here we are collecting proofs for later on bennhcmarking for j := 2; j <= 128; j = j * 2 { wsrc.SetRingSize(j) - tx, err := wsrc.TransferPayload0([]rpc.Transfer{rpc.Transfer{Destination: wdst.GetAddress().String(), Amount: 1}}, 0, false, rpc.Arguments{}, false) + tx, err := wsrc.TransferPayload0([]rpc.Transfer{rpc.Transfer{Destination: wdst.GetAddress().String(), Amount: 1}}, 0, false, rpc.Arguments{}, 0, false) if err != nil { t.Fatalf("Cannot create transaction, err %s", err) } else { @@ -228,7 +228,7 @@ func Test_Creation_TX(t *testing.T) { wdst.SetRingSize(2) // accounts are reversed - reverse_tx, err := wdst.TransferPayload0([]rpc.Transfer{rpc.Transfer{Destination: wsrc.GetAddress().String(), Amount: 1}}, 0, false, rpc.Arguments{}, false) + reverse_tx, err := wdst.TransferPayload0([]rpc.Transfer{rpc.Transfer{Destination: wsrc.GetAddress().String(), Amount: 1}}, 0, false, rpc.Arguments{}, 0, false) if err != nil { t.Fatalf("Cannot create transaction, err %s", err) } @@ -242,7 +242,7 @@ func Test_Creation_TX(t *testing.T) { pre_transfer_src_balance, _ := wsrc.Get_Balance() pre_transfer_dst_balance, _ := wdst.Get_Balance() - tx, err := wsrc.TransferPayload0([]rpc.Transfer{rpc.Transfer{Destination: wdst.GetAddress().String(), Amount: 1}}, 0, false, rpc.Arguments{}, false) + tx, err := wsrc.TransferPayload0([]rpc.Transfer{rpc.Transfer{Destination: wdst.GetAddress().String(), Amount: 1}}, 0, false, rpc.Arguments{}, 0, false) if err != nil { t.Fatalf("Cannot create transaction, err %s", err) } else { @@ -284,7 +284,7 @@ func Test_Creation_TX(t *testing.T) { var tx_set []*transaction.Transaction for i := 0; i < 6; i++ { - tx, err := wsrc.TransferPayload0([]rpc.Transfer{rpc.Transfer{Destination: wdst.GetAddress().String(), Amount: 1}}, 0, false, rpc.Arguments{}, false) + tx, err := wsrc.TransferPayload0([]rpc.Transfer{rpc.Transfer{Destination: wdst.GetAddress().String(), Amount: 1}}, 0, false, rpc.Arguments{}, 0, false) if err != nil { t.Fatalf("Cannot create transaction, err %s", err) } else { diff --git a/cmd/simulator/simulator.go b/cmd/simulator/simulator.go index 919fd86..b0618d9 100644 --- a/cmd/simulator/simulator.go +++ b/cmd/simulator/simulator.go @@ -21,6 +21,7 @@ import "os" import "time" import "fmt" import "bytes" +import "errors" import "strings" import "strconv" @@ -67,7 +68,7 @@ DERO : A secure, private blockchain with smart-contracts Simulates DERO block single node which helps in development and tests Usage: - simulator [--help] [--version] [--testnet] [--debug] [--sync-node] [--data-dir=] [--rpc-bind=<127.0.0.1:9999>] [--http-address=<0.0.0.0:8080>] [--clog-level=1] [--flog-level=1] + simulator [--help] [--version] [--testnet] [--debug] [--noautomine] [--sync-node] [--data-dir=] [--rpc-bind=<127.0.0.1:9999>] [--http-address=<0.0.0.0:8080>] [--clog-level=1] [--flog-level=1] simulator -h | --help simulator --version @@ -76,6 +77,7 @@ Options: --version Show version. --testnet Run in testnet mode. --debug Debug mode enabled, print more log messages + --noautomine No blocks will be mined (except genesis), used for testing, supported only on linux --clog-level=1 Set console log level (0 to 127) --flog-level=1 Set file log level (0 to 127) --data-dir= Store blockchain data at this location @@ -89,6 +91,8 @@ var logger logr.Logger var rpcport = "127.0.0.1:20000" +var TRIGGER_MINE_BLOCK string = "/dev/shm/mineblocknow" + const wallet_ports_start = 30000 // all wallets will rpc activated on ports // this is a crude function used during tests @@ -100,16 +104,17 @@ func Mine_block_single(chain *blockchain.Blockchain, miner_address rpc.Address) // return fmt.Errorf("this function can only run in simulator mode") //} - for i := uint64(0); i < config.BLOCK_TIME-config.MINIBLOCK_HIGHDIFF; i++ { + for { bl, mbl, _, _, err := chain.Create_new_block_template_mining(miner_address) if err != nil { logger.Error(err, "err while request block template") return err } - if _, blid, _, err = chain.Accept_new_block(bl.Timestamp, mbl.Serialize()); blid.IsZero() || err != nil { - if err != nil { - logger.Error(err, "err while accepting block template") - } + if _, blid, _, err = chain.Accept_new_block(bl.Timestamp, mbl.Serialize()); err != nil { + logger.Error(err, "err while accepting block template") + return err + } else if !blid.IsZero() { + break } } return nil @@ -546,26 +551,46 @@ exit: } } +func exists(path string) bool { + _, err := os.Stat(path) + return !errors.Is(err, os.ErrNotExist) +} + +func trigger_block_creation() { + fmt.Printf("triggering fie creation\n") + if globals.Arguments["--noautomine"].(bool) == true { + err := os.WriteFile(TRIGGER_MINE_BLOCK, []byte("HELLO"), 0666) + fmt.Printf("triggering fie creation %s err %s\n", TRIGGER_MINE_BLOCK, err) + } +} + // generate a block as soon as tx appears in blockchain // or 15 sec pass func mine_block_auto(chain *blockchain.Blockchain, miner_address rpc.Address) { + last_block_time := time.Now() for { bl, _, _, _, err := chain.Create_new_block_template_mining(miner_address) if err != nil { logger.Error(err, "error while building mining block") + continue } - if time.Now().Sub(last_block_time) > time.Duration(config.BLOCK_TIME)*time.Second || // every X secs generate a block - len(bl.Tx_hashes) >= 1 { //pools have a tx, try to mine them ASAP - - if err := Mine_block_single(chain, miner_address); err != nil { - time.Sleep(time.Second) - continue + if globals.Arguments["--noautomine"].(bool) == true && exists(TRIGGER_MINE_BLOCK) { + if err = Mine_block_single(chain, miner_address); err == nil { + last_block_time = time.Now() + os.Remove(TRIGGER_MINE_BLOCK) + } else { + logger.Error(err, "error while mining single block") + } + } + if globals.Arguments["--noautomine"].(bool) == false { + if time.Now().Sub(last_block_time) > time.Duration(config.BLOCK_TIME)*time.Second || // every X secs generate a block + len(bl.Tx_hashes) >= 1 { //pools have a tx, try to mine them ASAP + if err := Mine_block_single(chain, miner_address); err == nil { + last_block_time = time.Now() + } } - - last_block_time = time.Now() - } time.Sleep(900 * time.Millisecond) } diff --git a/config/config.go b/config/config.go index 8907cf7..0305005 100644 --- a/config/config.go +++ b/config/config.go @@ -39,8 +39,12 @@ const SC_META = "M" // keeps all SCs balance, their state, their OWNER, the // one are open SCs, which provide i/o privacy // one are private SCs which are truly private, in which no one has visibility of io or functionality -// 10.25 MB block every 12 secs is equal to roughly 410 TX per second -// if we consider side blocks, TPS increase to > 500 TPS +// this limits the contract size or amount of data it can store per interaction +const MAX_STORAGE_GAS_ATOMIC_UNITS = 20000 + +// Minimum FEE calculation constants are here +const FEE_PER_KB = uint64(100) // .00100 dero per kb + // we can easily improve TPS by changing few parameters in this file // the resources compute/network may not be easy for the developing countries // we need to trade of TPS as per community @@ -51,9 +55,6 @@ const STARGATE_HE_MAX_TX_SIZE = 300 * 1024 // max size const MIN_RINGSIZE = 2 // >= 2 , ringsize will be accepted const MAX_RINGSIZE = 128 // <= 128, ringsize will be accepted -// Minimum FEE calculation constants are here -const FEE_PER_KB = uint64(100) // .00100 dero per kb - type SettingsStruct struct { MAINNET_BOOTSTRAP_DIFFICULTY uint64 `env:"MAINNET_BOOTSTRAP_DIFFICULTY" envDefault:"80000000"` MAINNET_MINIMUM_DIFFICULTY uint64 `env:"MAINNET_MINIMUM_DIFFICULTY" envDefault:"80000000"` @@ -106,7 +107,7 @@ var Mainnet = CHAIN_CONFIG{Name: "mainnet", } var Testnet = CHAIN_CONFIG{Name: "testnet", // testnet will always have last 3 bytes 0 - Network_ID: uuid.FromBytesOrNil([]byte{0x59, 0xd7, 0xf7, 0xe9, 0xdd, 0x48, 0xd5, 0xfd, 0x13, 0x0a, 0xf6, 0xe0, 0x77, 0x00, 0x00, 0x00}), + Network_ID: uuid.FromBytesOrNil([]byte{0x59, 0xd7, 0xf7, 0xe9, 0xdd, 0x48, 0xd5, 0xfd, 0x13, 0x0a, 0xf6, 0xe0, 0x78, 0x00, 0x00, 0x00}), GETWORK_Default_Port: 10100, P2P_Default_Port: 40401, RPC_Default_Port: 40402, diff --git a/config/version.go b/config/version.go index 407f7e9..4abd40d 100644 --- a/config/version.go +++ b/config/version.go @@ -20,4 +20,4 @@ import "github.com/blang/semver/v4" // right now it has to be manually changed // do we need to include git commitsha?? -var Version = semver.MustParse("3.4.99-0.DEROHE.STARGATE+03012022") +var Version = semver.MustParse("3.4.103-0.DEROHE.STARGATE+18012022") diff --git a/dvm/dvm.go b/dvm/dvm.go index 2e7b65e..4421fbb 100644 --- a/dvm/dvm.go +++ b/dvm/dvm.go @@ -157,10 +157,10 @@ func check_valid_name(name string) bool { } func check_valid_type(name string) Vtype { - switch name { - case "Uint64": + switch strings.ToLower(name) { + case "uint64": return Uint64 - case "String": + case "string": return String } return Invalid @@ -405,10 +405,17 @@ type Blockchain_Input struct { // all storage state is shared, this means something similar to solidity delegatecall // this is necessary to prevent number of attacks type Shared_State struct { - SCIDZERO crypto.Hash // points to DERO SCID , which is zero - SCIDSELF crypto.Hash // points to SELF SCID - Persistance bool // whether the results will be persistant or it's just a demo/test call - Trace bool // enables tracing to screen + SCIDZERO crypto.Hash // points to DERO SCID , which is zero + SCIDSELF crypto.Hash // points to SELF SCID, this separation is necessary, if we enable cross SC calls + // but note they bring all sorts of mess, bugs + Persistance bool // whether the results will be persistant or it's just a demo/test call + Trace bool // enables tracing to screen + GasComputeUsed int64 + GasComputeLimit int64 + GasComputeCheck bool // if gascheck is true, bail out as soon as limit is breached + GasStoreUsed int64 + GasStoreLimit int64 + GasStoreCheck bool // storage gas, bail out as soon as limit is breached Chain_inputs *Blockchain_Input // all blockchain info is available here @@ -416,6 +423,8 @@ type Shared_State struct { Assets_Transfer map[string]map[string]uint64 // any Assets that this TX wants to send OUT // transfers are only processed after the contract has terminated successfully + RamStore map[Variable]Variable + RND *RND // this is initialized only once while invoking entrypoint Store *TX_Storage // mechanism to access a data store, can discard changes @@ -425,10 +434,28 @@ type Shared_State struct { } +//consumr and check compute gas +func (state *Shared_State) ConsumeGas(c int64) { + if state != nil { + state.GasComputeUsed += c + if state.GasComputeCheck && state.GasComputeUsed > state.GasComputeLimit { + panic("Insufficient Gas") + } + } +} + +// consume and check storage gas +func (state *Shared_State) ConsumeStorageGas(c int64) { + if state != nil { + state.GasStoreUsed += c + if state.GasStoreCheck && state.GasStoreUsed > state.GasStoreLimit { + panic("Insufficient Storage Gas") + } + } +} + type DVM_Interpreter struct { Version semver.Version // current version set by setversion - Cost int64 - CostLimit int64 SCID string SC *SmartContract EntryPoint string @@ -449,10 +476,8 @@ type DVM_Interpreter struct { func (i *DVM_Interpreter) incrementIP(newip uint64) (line []string, err error) { var ok bool + try_again: - - // increase cost here - if newip == 0 { // we are simply falling through index := i.f.LinesNumberIndex[i.IP] // find the pos in the line numbers and find the current line_number index++ @@ -493,10 +518,12 @@ func (i *DVM_Interpreter) interpret_SmartContract() (err error) { return } + i.State.ConsumeGas(5000) // every line number has some gas costs + newIP = 0 // this is necessary otherwise, it will trigger an infinite loop in the case given below /* - * Function SetOwner(value Uint64, newowner String) Uint64 + * Function SetOwner(value Uint64, newowner String) Uint64 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 20 RETURN 1 30 STORE("owner",newowner) @@ -963,6 +990,8 @@ func IsZero(value interface{}) uint64 { func (dvm *DVM_Interpreter) evalBinaryExpr(exp *ast.BinaryExpr) interface{} { + dvm.State.ConsumeGas(800) // every expr evaluation has some cost + left := dvm.eval(exp.X) right := dvm.eval(exp.Y) @@ -992,7 +1021,6 @@ func (dvm *DVM_Interpreter) evalBinaryExpr(exp *ast.BinaryExpr) interface{} { return uint64(1) } return uint64(0) - } // handle string operands @@ -1002,6 +1030,9 @@ func (dvm *DVM_Interpreter) evalBinaryExpr(exp *ast.BinaryExpr) interface{} { switch exp.Op { case token.ADD: + if len(left_string)+len(right_string) >= 1024*1024 { + panic("too big string value") + } return left_string + right_string case token.EQL: if left_string == right_string { @@ -1016,7 +1047,6 @@ func (dvm *DVM_Interpreter) evalBinaryExpr(exp *ast.BinaryExpr) interface{} { default: panic(fmt.Sprintf("String data type only support addition operation ('%s') not supported", exp.Op)) } - } left_uint64 := left.(uint64) @@ -1071,65 +1101,7 @@ func (dvm *DVM_Interpreter) evalBinaryExpr(exp *ast.BinaryExpr) interface{} { return uint64(1) } default: - panic("This operation cannot be handled") + panic("This operation cannot be handled") } return uint64(0) } - -/* -func main() { - - const src = ` - Function HelloWorld(s Uint64) Uint64 - - 5 - 10 Dim x1, x2 as Uint64 - 20 LET x1 = 3 - 25 LET x1 = 3 + 5 - 1 - 27 LET x2 = x1 + 3 - 28 RETURN HelloWorld2(s*s) - 30 Printf "x1=%d x2=%d s = %d" x1 x2 s - 35 IF x1 == 7 THEN GOTO 100 ELSE GOTO 38 - 38 Dim y1, y2 as String - 40 LET y1 = "first string" + "second string" - - - 60 GOTO 100 - 80 GOTO 10 - 100 RETURN 0 - 500 LET y = 45 - 501 y = 45 - - End Function - - - Function HelloWorld2(s Uint64) Uint64 - - 900 Return s - 950 y = 45 - // Comment begins at column 5. - - ; This line should not be included in the output. -REM jj - - - -7000 let x.ku[1+1]=0x55 -End Function - -` - - // we should be build an AST here - - sc, pos, err := ParseSmartContract(src) - if err != nil { - fmt.Printf("Error while parsing smart contract pos %s err : %s\n", pos, err) - return - } - - result, err := RunSmartContract(&sc, "HelloWorld", map[string]interface{}{"s": "9999"}) - - fmt.Printf("result %+v err %s\n", result, err) - -} -*/ diff --git a/dvm/dvm_functions.go b/dvm/dvm_functions.go index 6b9ade2..010e136 100644 --- a/dvm/dvm_functions.go +++ b/dvm/dvm_functions.go @@ -39,74 +39,81 @@ import "github.com/deroproject/derohe/cryptography/crypto" // this needs more investigation // also, more investigation is required to enable predetermined external oracles -type DVM_FUNCTION_PTR func(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) +type DVM_FUNCTION_PTR_UINT64 func(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) +type DVM_FUNCTION_PTR_STRING func(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result string) +type DVM_FUNCTION_PTR_ANY func(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) var func_table = map[string][]func_data{} type func_data struct { - Range semver.Range - Cost int64 - Ptr DVM_FUNCTION_PTR + Range semver.Range + ComputeCost int64 + StorageCost int64 + PtrU DVM_FUNCTION_PTR_UINT64 + PtrS DVM_FUNCTION_PTR_STRING + Ptr DVM_FUNCTION_PTR_ANY } func init() { - func_table["version"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_version}} - func_table["load"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_load}} - func_table["exists"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_exists}} - func_table["store"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_store}} - func_table["delete"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_delete}} - func_table["random"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_random}} - func_table["scid"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_scid}} - func_table["blid"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_blid}} - func_table["txid"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_txid}} - func_table["dero"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_dero}} - func_table["block_height"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_block_height}} - func_table["block_timestamp"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_block_timestamp}} - func_table["signer"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_signer}} - func_table["update_sc_code"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_update_sc_code}} - func_table["is_address_valid"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_is_address_valid}} - func_table["address_raw"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_address_raw}} - func_table["address_string"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_address_string}} - func_table["send_dero_to_address"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_send_dero_to_address}} - func_table["send_asset_to_address"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_send_asset_to_address}} - func_table["derovalue"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_derovalue}} - func_table["assetvalue"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_assetvalue}} - func_table["atoi"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_atoi}} - func_table["itoa"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_itoa}} - func_table["sha256"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_sha256}} - func_table["sha3256"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_sha3256}} - func_table["keccak256"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_keccak256}} - func_table["hex"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_hex}} - func_table["hexdecode"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_hexdecode}} - func_table["min"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_min}} - func_table["max"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_max}} - func_table["strlen"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_strlen}} - func_table["substr"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), Cost: 1, Ptr: dvm_substr}} -} - -func (dvm *DVM_Interpreter) process_cost(c int64) { - dvm.Cost += c - - // TODO check cost overflow + func_table["version"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 1000, StorageCost: 0, PtrU: dvm_version}} + func_table["load"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 5000, StorageCost: 0, Ptr: dvm_load}} + func_table["exists"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 5000, StorageCost: 0, PtrU: dvm_exists}} + func_table["store"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 10000, StorageCost: 0, PtrU: dvm_store}} + func_table["delete"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 3000, StorageCost: 0, PtrU: dvm_delete}} + func_table["mapexists"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 1000, StorageCost: 0, PtrU: dvm_mapexists}} + func_table["mapget"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 1000, StorageCost: 0, Ptr: dvm_mapget}} + func_table["mapstore"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 1000, StorageCost: 0, PtrU: dvm_mapstore}} + func_table["mapdelete"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 1000, StorageCost: 0, PtrU: dvm_mapdelete}} + func_table["random"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 2500, StorageCost: 0, PtrU: dvm_random}} + func_table["scid"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 2000, StorageCost: 0, PtrS: dvm_scid}} + func_table["blid"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 2000, StorageCost: 0, PtrS: dvm_blid}} + func_table["txid"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 2000, StorageCost: 0, PtrS: dvm_txid}} + func_table["dero"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 10000, StorageCost: 0, PtrS: dvm_dero}} + func_table["block_height"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 2000, StorageCost: 0, PtrU: dvm_block_height}} + func_table["block_timestamp"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 2500, StorageCost: 0, PtrU: dvm_block_timestamp}} + func_table["signer"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 5000, StorageCost: 0, PtrS: dvm_signer}} + func_table["update_sc_code"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 5000, StorageCost: 0, PtrU: dvm_update_sc_code}} + func_table["is_address_valid"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 50000, StorageCost: 0, PtrU: dvm_is_address_valid}} + func_table["address_raw"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 60000, StorageCost: 0, PtrS: dvm_address_raw}} + func_table["address_string"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 50000, StorageCost: 0, PtrS: dvm_address_string}} + func_table["send_dero_to_address"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 70000, StorageCost: 0, PtrU: dvm_send_dero_to_address}} + func_table["send_asset_to_address"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 90000, StorageCost: 0, PtrU: dvm_send_asset_to_address}} + func_table["derovalue"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 10000, StorageCost: 0, PtrU: dvm_derovalue}} + func_table["assetvalue"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 10000, StorageCost: 0, PtrU: dvm_assetvalue}} + func_table["atoi"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 5000, StorageCost: 0, PtrU: dvm_atoi}} + func_table["itoa"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 5000, StorageCost: 0, PtrS: dvm_itoa}} + func_table["sha256"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 25000, StorageCost: 0, PtrS: dvm_sha256}} + func_table["sha3256"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 25000, StorageCost: 0, PtrS: dvm_sha3256}} + func_table["keccak256"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 25000, StorageCost: 0, PtrS: dvm_keccak256}} + func_table["hex"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 10000, StorageCost: 0, PtrS: dvm_hex}} + func_table["hexdecode"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 10000, StorageCost: 0, PtrS: dvm_hexdecode}} + func_table["min"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 5000, StorageCost: 0, PtrU: dvm_min}} + func_table["max"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 5000, StorageCost: 0, PtrU: dvm_max}} + func_table["strlen"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 20000, StorageCost: 0, PtrU: dvm_strlen}} + func_table["substr"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 20000, StorageCost: 0, PtrS: dvm_substr}} + func_table["panic"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 10000, StorageCost: 0, PtrU: dvm_panic}} } // this will handle all internal functions which may be required/necessary to expand DVM functionality func (dvm *DVM_Interpreter) Handle_Internal_Function(expr *ast.CallExpr, func_name string) (handled bool, result interface{}) { - var err error - _ = err - if funcs, ok := func_table[strings.ToLower(func_name)]; ok { - for _, f := range funcs { + if func_data_array, ok := func_table[strings.ToLower(func_name)]; ok { + for _, f := range func_data_array { if f.Range(dvm.Version) { - dvm.process_cost(f.Cost) - return f.Ptr(dvm, expr) + dvm.State.ConsumeGas(f.ComputeCost) + if f.PtrU != nil { + return f.PtrU(dvm, expr) + } else if f.PtrS != nil { + return f.PtrS(dvm, expr) + } else { + return f.Ptr(dvm, expr) + } } } - - return false, nil + panic("function doesnot match any version") } - - return false, nil + //panic("function does not exist") + return false, nil // function does not exist } // the load/store functions are sandboxed and thus cannot affect any other SC storage @@ -124,7 +131,6 @@ func (dvm *DVM_Interpreter) Load(key Variable) interface{} { default: panic("Unhandled data_type") } - } // whether a variable exists in store or not @@ -142,10 +148,28 @@ func (dvm *DVM_Interpreter) Delete(key Variable) { dvm.State.Store.Delete(DataKey{SCID: dvm.State.Chain_inputs.SCID, Key: key}) } -func dvm_version(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 1 { // expression without limit - panic("version expects 1 parameters") +// we should migrate to generics ASAP +func convertdatatovariable(datai interface{}) Variable { + switch k := datai.(type) { + case uint64: + return Variable{Type: Uint64, ValueUint64: k} + case string: + return Variable{Type: String, ValueString: k} + default: + panic("This variable cannot be loaded") } +} + +// checks whether necessary number of arguments have been provided +func checkargscount(expected, actual int) { + if expected != actual { + panic("incorrect number of arguments") + } +} + +func dvm_version(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { + checkargscount(1, len(expr.Args)) // check number of arguments + if version_str, ok := dvm.eval(expr.Args[0]).(string); !ok { panic("unsupported version format") } else { @@ -155,86 +179,78 @@ func dvm_version(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result } func dvm_load(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 1 { - panic("Load function expects a single varible as parameter") - } + checkargscount(1, len(expr.Args)) // check number of arguments key := dvm.eval(expr.Args[0]) - switch k := key.(type) { - case uint64: - return true, dvm.Load(Variable{Type: Uint64, ValueUint64: k}) - case string: - return true, dvm.Load(Variable{Type: String, ValueString: k}) - default: - panic("This variable cannot be loaded") - } + return true, dvm.Load(convertdatatovariable(key)) + } -func dvm_exists(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 1 { - panic("Exists function expects a single varible as parameter") - } - key := dvm.eval(expr.Args[0]) // evaluate the argument and use the result - switch k := key.(type) { - case uint64: - return true, dvm.Exists(Variable{Type: Uint64, ValueUint64: k}) - case string: - return true, dvm.Exists(Variable{Type: String, ValueString: k}) - default: - panic("This variable cannot be loaded") - } +func dvm_exists(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { + checkargscount(1, len(expr.Args)) // check number of arguments + key := dvm.eval(expr.Args[0]) // evaluate the argument and use the result + return true, dvm.Exists(convertdatatovariable(key)) } -func dvm_store(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 2 { - panic("Store function expects 2 variables as parameter") - } - key_eval := dvm.eval(expr.Args[0]) - value_eval := dvm.eval(expr.Args[1]) - var key, value Variable - switch k := key_eval.(type) { - case uint64: - key = Variable{Type: Uint64, ValueUint64: k} - - case string: - key = Variable{Type: String, ValueString: k} - default: - panic("This variable cannot be stored") - } - - switch k := value_eval.(type) { - case uint64: - value = Variable{Type: Uint64, ValueUint64: k} - case string: - value = Variable{Type: String, ValueString: k} - default: - panic("This variable cannot be stored") - } +func dvm_store(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { + checkargscount(2, len(expr.Args)) // check number of arguments + key := convertdatatovariable(dvm.eval(expr.Args[0])) + value := convertdatatovariable(dvm.eval(expr.Args[1])) dvm.Store(key, value) - return true, nil + return true, 1 } -func dvm_delete(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 1 { - panic("Delete function expects 2 variables as parameter") - } - key_eval := dvm.eval(expr.Args[0]) - var key Variable - switch k := key_eval.(type) { - case uint64: - key = Variable{Type: Uint64, ValueUint64: k} - - case string: - key = Variable{Type: String, ValueString: k} - default: - panic("This variable cannot be deleted") - } - +func dvm_delete(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { + checkargscount(1, len(expr.Args)) // check number of arguments + key := convertdatatovariable(dvm.eval(expr.Args[0])) dvm.Delete(key) - return true, nil + return true, uint64(1) } -func dvm_random(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { +func dvm_mapexists(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { + checkargscount(1, len(expr.Args)) // check number of arguments + key := convertdatatovariable(dvm.eval(expr.Args[0])) // evaluate the argument and use the result + + if _, ok := dvm.State.RamStore[key]; ok { + return true, uint64(1) + } + return true, uint64(0) + +} + +func dvm_mapget(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { + checkargscount(1, len(expr.Args)) // check number of arguments + key := convertdatatovariable(dvm.eval(expr.Args[0])) // evaluate the argument and use the result + + v := dvm.State.RamStore[key] + + if v.Type == Uint64 { + return true, v.ValueUint64 + } else if v.Type == String { + return true, v.ValueString + } else { + panic("This variable cannot be obtained") + } +} + +func dvm_mapstore(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { + checkargscount(2, len(expr.Args)) // check number of arguments + key := convertdatatovariable(dvm.eval(expr.Args[0])) // evaluate the argument and use the result + value := convertdatatovariable(dvm.eval(expr.Args[1])) // evaluate the argument and use the result + + dvm.State.RamStore[key] = value + return true, uint64(1) +} + +func dvm_mapdelete(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { + checkargscount(1, len(expr.Args)) // check number of arguments + key := convertdatatovariable(dvm.eval(expr.Args[0])) // evaluate the argument and use the result + + delete(dvm.State.RamStore, key) + return true, uint64(1) +} + +func dvm_random(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { if len(expr.Args) >= 2 { panic("RANDOM function expects 0 or 1 number as parameter") } @@ -252,36 +268,26 @@ func dvm_random(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result } } -func dvm_scid(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 0 { - panic("SCID function expects 0 parameters") - } +func dvm_scid(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result string) { + checkargscount(0, len(expr.Args)) // check number of arguments return true, string(dvm.State.Chain_inputs.SCID[:]) } -func dvm_blid(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 0 { - panic("blid function expects 0 parameters") - } +func dvm_blid(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result string) { + checkargscount(0, len(expr.Args)) // check number of arguments return true, string(dvm.State.Chain_inputs.BLID[:]) } -func dvm_txid(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 0 { - panic("txid function expects 0 parameters") - } +func dvm_txid(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result string) { + checkargscount(0, len(expr.Args)) // check number of arguments return true, string(dvm.State.Chain_inputs.TXID[:]) } -func dvm_dero(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 0 { - panic("dero function expects 0 parameters") - } +func dvm_dero(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result string) { + checkargscount(0, len(expr.Args)) // check number of arguments var zerohash crypto.Hash return true, string(zerohash[:]) } -func dvm_block_height(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 0 { - panic("BLOCK_HEIGHT function expects 0 parameters") - } +func dvm_block_height(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { + checkargscount(0, len(expr.Args)) // check number of arguments return true, dvm.State.Chain_inputs.BL_HEIGHT } @@ -293,24 +299,18 @@ func dvm_block_topoheight(dvm *DVM_Interpreter, expr *ast.CallExpr)(handled bool return true, dvm.State.Chain_inputs.BL_TOPOHEIGHT } */ -func dvm_block_timestamp(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 0 { - panic("BLOCK_TIMESTAMP function expects 0 parameters") - } +func dvm_block_timestamp(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { + checkargscount(0, len(expr.Args)) // check number of arguments return true, dvm.State.Chain_inputs.BL_TIMESTAMP } -func dvm_signer(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 0 { - panic("SIGNER function expects 0 parameters") - } +func dvm_signer(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result string) { + checkargscount(0, len(expr.Args)) // check number of arguments return true, dvm.State.Chain_inputs.Signer } -func dvm_update_sc_code(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 1 { - panic("UPDATE_SC_CODE function expects 1 parameters") - } +func dvm_update_sc_code(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { + checkargscount(1, len(expr.Args)) // check number of arguments code_eval := dvm.eval(expr.Args[0]) switch k := code_eval.(type) { case string: @@ -321,10 +321,8 @@ func dvm_update_sc_code(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, } } -func dvm_is_address_valid(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 1 { - panic("IS_ADDRESS_VALID function expects 1 parameters") - } +func dvm_is_address_valid(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { + checkargscount(1, len(expr.Args)) // check number of arguments addr_eval := dvm.eval(expr.Args[0]) switch k := addr_eval.(type) { @@ -341,10 +339,8 @@ func dvm_is_address_valid(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled boo } } -func dvm_address_raw(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 1 { - panic("ADDRESS_RAW function expects 1 parameters") - } +func dvm_address_raw(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result string) { + checkargscount(1, len(expr.Args)) // check number of arguments addr_eval := dvm.eval(expr.Args[0]) switch k := addr_eval.(type) { @@ -353,16 +349,14 @@ func dvm_address_raw(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, re return true, string(addr.Compressed()) } - return true, nil // fallthrough not supported in type switch + return true, "" // fallthrough not supported in type switch default: - return true, nil + return true, "" } } -func dvm_address_string(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 1 { - panic("ADDRESS_STRING function expects 1 parameters") - } +func dvm_address_string(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result string) { + checkargscount(1, len(expr.Args)) // check number of arguments addr_eval := dvm.eval(expr.Args[0]) switch k := addr_eval.(type) { @@ -374,16 +368,14 @@ func dvm_address_string(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, return true, addr.String() } - return true, nil // fallthrough not supported in type switch + return true, "" // fallthrough not supported in type switch default: - return true, nil + return true, "" } } -func dvm_send_dero_to_address(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 2 { - panic("SEND_DERO_TO_ADDRESS function expects 2 parameters") - } +func dvm_send_dero_to_address(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { + checkargscount(2, len(expr.Args)) // check number of arguments addr_eval := dvm.eval(expr.Args[0]) amount_eval := dvm.eval(expr.Args[1]) @@ -397,16 +389,14 @@ func dvm_send_dero_to_address(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled } if amount_eval.(uint64) == 0 { - return true, amount_eval + return true, amount_eval.(uint64) } var zerohash crypto.Hash dvm.State.Store.SendExternal(dvm.State.Chain_inputs.SCID, zerohash, addr_eval.(string), amount_eval.(uint64)) // add record for external transfer - return true, amount_eval + return true, amount_eval.(uint64) } -func dvm_send_asset_to_address(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 3 { - panic("SEND_ASSET_TO_ADDRESS function expects 3 parameters") // address, amount, asset - } +func dvm_send_asset_to_address(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { + checkargscount(3, len(expr.Args)) // check number of arguments addr_eval := dvm.eval(expr.Args[0]) amount_eval := dvm.eval(expr.Args[1]) @@ -427,7 +417,7 @@ func dvm_send_asset_to_address(dvm *DVM_Interpreter, expr *ast.CallExpr) (handle //fmt.Printf("sending asset %x (%d) to address %x\n", asset_eval.(string), amount_eval.(uint64),[]byte(addr_eval.(string))) if amount_eval.(uint64) == 0 { - return true, amount_eval + return true, amount_eval.(uint64) } if len(asset_eval.(string)) != 32 { @@ -438,79 +428,72 @@ func dvm_send_asset_to_address(dvm *DVM_Interpreter, expr *ast.CallExpr) (handle dvm.State.Store.SendExternal(dvm.State.Chain_inputs.SCID, asset, addr_eval.(string), amount_eval.(uint64)) // add record for external transfer - return true, amount_eval + return true, amount_eval.(uint64) } -func dvm_derovalue(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 0 { // expression without limit - panic("DEROVALUE expects no parameters") - } else { - return true, dvm.State.Assets[dvm.State.SCIDZERO] - } +func dvm_derovalue(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { + checkargscount(0, len(expr.Args)) // check number of arguments + return true, dvm.State.Assets[dvm.State.SCIDZERO] + } -func dvm_assetvalue(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 1 { // expression without limit - panic("assetVALUE expects 1 parameters") - } else { - asset_eval := dvm.eval(expr.Args[0]) +func dvm_assetvalue(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { + checkargscount(1, len(expr.Args)) // check number of arguments - if _, ok := asset_eval.(string); !ok { - panic("asset must be valid string") - } - if len(asset_eval.(string)) != 32 { - panic("asset must be valid string of 32 byte length") - } - var asset crypto.Hash - copy(asset[:], ([]byte(asset_eval.(string)))) + asset_eval := dvm.eval(expr.Args[0]) - return true, dvm.State.Assets[asset] + if _, ok := asset_eval.(string); !ok { + panic("asset must be valid string") } + if len(asset_eval.(string)) != 32 { + panic("asset must be valid string of 32 byte length") + } + var asset crypto.Hash + copy(asset[:], ([]byte(asset_eval.(string)))) + + return true, dvm.State.Assets[asset] + } -func dvm_itoa(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 1 { // expression without limit - panic("itoa expects 1 parameters") - } else { - asset_eval := dvm.eval(expr.Args[0]) +func dvm_itoa(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result string) { + checkargscount(1, len(expr.Args)) // check number of arguments - if _, ok := asset_eval.(uint64); !ok { - panic("itoa argument must be valid uint64") - } + asset_eval := dvm.eval(expr.Args[0]) - return true, fmt.Sprintf("%d", asset_eval.(uint64)) + if _, ok := asset_eval.(uint64); !ok { + panic("itoa argument must be valid uint64") } + + return true, fmt.Sprintf("%d", asset_eval.(uint64)) + } -func dvm_atoi(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 1 { // expression without limit - panic("itoa expects 1 parameters") - } else { - asset_eval := dvm.eval(expr.Args[0]) +func dvm_atoi(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { + checkargscount(1, len(expr.Args)) // check number of arguments - if _, ok := asset_eval.(string); !ok { - panic("atoi argument must be valid string") - } + asset_eval := dvm.eval(expr.Args[0]) - if u, err := strconv.ParseUint(asset_eval.(string), 10, 64); err != nil { - panic(err) - } else { - return true, u - } + if _, ok := asset_eval.(string); !ok { + panic("atoi argument must be valid string") } + + if u, err := strconv.ParseUint(asset_eval.(string), 10, 64); err != nil { + panic(err) + } else { + return true, u + } + } -func dvm_strlen(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 1 { // expression without limit - panic("itoa expects 1 parameters") - } else { - asset_eval := dvm.eval(expr.Args[0]) +func dvm_strlen(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { + checkargscount(1, len(expr.Args)) // check number of arguments + asset_eval := dvm.eval(expr.Args[0]) - if _, ok := asset_eval.(string); !ok { - panic("atoi argument must be valid string") - } - return true, uint64(len([]byte(asset_eval.(string)))) + if _, ok := asset_eval.(string); !ok { + panic("atoi argument must be valid string") } + return true, uint64(len([]byte(asset_eval.(string)))) + } func substr(input string, start uint64, length uint64) string { @@ -527,10 +510,8 @@ func substr(input string, start uint64, length uint64) string { return string(asbytes[start : start+length]) } -func dvm_substr(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 3 { // expression without limit - panic("substr expects 3 parameters") - } +func dvm_substr(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result string) { + checkargscount(3, len(expr.Args)) // check number of arguments input_eval := dvm.eval(expr.Args[0]) if _, ok := input_eval.(string); !ok { panic("input argument must be valid string") @@ -547,10 +528,8 @@ func dvm_substr(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result return true, substr(input_eval.(string), offset_eval.(uint64), length_eval.(uint64)) } -func dvm_sha256(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 1 { // expression without limit - panic("sha256 expects 1 parameters") - } +func dvm_sha256(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result string) { + checkargscount(1, len(expr.Args)) // check number of arguments input_eval := dvm.eval(expr.Args[0]) if _, ok := input_eval.(string); !ok { panic("input argument must be valid string") @@ -560,10 +539,8 @@ func dvm_sha256(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result return true, string(hash[:]) } -func dvm_sha3256(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 1 { // expression without limit - panic("sha3256 expects 1 parameters") - } +func dvm_sha3256(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result string) { + checkargscount(1, len(expr.Args)) // check number of arguments input_eval := dvm.eval(expr.Args[0]) if _, ok := input_eval.(string); !ok { panic("input argument must be valid string") @@ -573,10 +550,8 @@ func dvm_sha3256(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result return true, string(hash[:]) } -func dvm_keccak256(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 1 { // expression without limit - panic("keccak256 expects 1 parameters") - } +func dvm_keccak256(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result string) { + checkargscount(1, len(expr.Args)) // check number of arguments input_eval := dvm.eval(expr.Args[0]) if _, ok := input_eval.(string); !ok { panic("input argument must be valid string") @@ -588,20 +563,16 @@ func dvm_keccak256(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, resu return true, string(hash[:]) } -func dvm_hex(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 1 { // expression without limit - panic("hex expects 1 parameters") - } +func dvm_hex(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result string) { + checkargscount(1, len(expr.Args)) // check number of arguments input_eval := dvm.eval(expr.Args[0]) if _, ok := input_eval.(string); !ok { panic("input argument must be valid string") } return true, hex.EncodeToString([]byte(input_eval.(string))) } -func dvm_hexdecode(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 1 { // expression without limit - panic("hex expects 1 parameters") - } +func dvm_hexdecode(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result string) { + checkargscount(1, len(expr.Args)) // check number of arguments input_eval := dvm.eval(expr.Args[0]) if _, ok := input_eval.(string); !ok { panic("input argument must be valid string") @@ -614,10 +585,9 @@ func dvm_hexdecode(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, resu } } -func dvm_min(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 2 { // expression without limit - panic("min expects 2 parameters") - } +func dvm_min(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { + checkargscount(2, len(expr.Args)) // check number of arguments + a1 := dvm.eval(expr.Args[0]) if _, ok := a1.(uint64); !ok { panic("input argument must be uint64") @@ -629,15 +599,13 @@ func dvm_min(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result int } if a1.(uint64) < a2.(uint64) { - return true, a1 + return true, a1.(uint64) } - return true, a2 + return true, a2.(uint64) } -func dvm_max(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { - if len(expr.Args) != 2 { // expression without limit - panic("min expects 2 parameters") - } +func dvm_max(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { + checkargscount(2, len(expr.Args)) // check number of arguments a1 := dvm.eval(expr.Args[0]) if _, ok := a1.(uint64); !ok { panic("input argument must be uint64") @@ -649,7 +617,12 @@ func dvm_max(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result int } if a1.(uint64) > a2.(uint64) { - return true, a1 + return true, a1.(uint64) } - return true, a2 + return true, a2.(uint64) +} + +func dvm_panic(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { + panic("panic function called") + return true, uint64(0) } diff --git a/dvm/dvm_functions_test.go b/dvm/dvm_functions_test.go index 3223e89..41d20bc 100644 --- a/dvm/dvm_functions_test.go +++ b/dvm/dvm_functions_test.go @@ -219,6 +219,53 @@ var execution_tests_functions = []struct { nil, Variable{Type: String, ValueString: string("")}, }, + { + "mapget()", + `Function TestRun(input String) String + 10 mapstore("input",input) + 30 return mapget("input") + mapget("input") + End Function`, + "TestRun", + map[string]interface{}{"input": string("0123456789")}, + nil, + Variable{Type: String, ValueString: string("01234567890123456789")}, + }, + { + "mapget()", + `Function TestRun(input String) String + 10 mapstore("input",input) + 15 mapstore("input",input+input) + 30 return mapget("input") + End Function`, + "TestRun", + map[string]interface{}{"input": string("0123456789")}, + nil, + Variable{Type: String, ValueString: string("01234567890123456789")}, + }, + + { + "mapexists()", + `Function TestRun(input String) Uint64 + 10 mapstore("input",input) + 30 return mapexists("input") + mapexists("input1") + End Function`, + "TestRun", + map[string]interface{}{"input": string("0123456789")}, + nil, + Variable{Type: Uint64, ValueUint64: uint64(1)}, + }, + { + "mapdelete()", + `Function TestRun(input String) Uint64 + 10 mapstore("input",input) + 15 mapdelete("input") + 30 return mapexists("input") + End Function`, + "TestRun", + map[string]interface{}{"input": string("0123456789")}, + nil, + Variable{Type: Uint64, ValueUint64: uint64(0)}, + }, } func decodeHex(s string) []byte { @@ -238,7 +285,7 @@ func Test_FUNCTION_execution(t *testing.T) { } state := &Shared_State{Chain_inputs: &Blockchain_Input{BL_HEIGHT: 5, BL_TIMESTAMP: 9, SCID: crypto.ZEROHASH, - BLID: crypto.ZEROHASH, TXID: crypto.ZEROHASH}} + BLID: crypto.ZEROHASH, TXID: crypto.ZEROHASH}, RamStore: map[Variable]Variable{}} result, err := RunSmartContract(&sc, test.EntryPoint, state, test.Args) switch { case test.Eerr == nil && err == nil: diff --git a/dvm/dvm_store.go b/dvm/dvm_store.go index 8596e11..18552aa 100644 --- a/dvm/dvm_store.go +++ b/dvm/dvm_store.go @@ -29,12 +29,6 @@ type DataKey struct { Key Variable } -type DataAtom struct { - Key DataKey - Prev_Value Variable // previous Value if any - Value Variable // current value if any -} - type TransferInternal struct { Asset crypto.Hash `cbor:"Asset,omitempty" json:"Asset,omitempty"` // transfer this asset SCID string `cbor:"A,omitempty" json:"A,omitempty"` // transfer to this SCID @@ -64,6 +58,8 @@ type TX_Storage struct { RawKeys map[string][]byte // this keeps the in-transit DB updates, just in case we have to discard instantly Transfers map[crypto.Hash]SC_Transfers // all transfers ( internal/external ) + + State *Shared_State // only for book keeping of storage gas } // initialize tx store @@ -82,17 +78,13 @@ func (tx_store *TX_Storage) RawLoad(key []byte) (value []byte, found bool) { return } -func (tx_store *TX_Storage) RawStore(key []byte, value []byte) { - tx_store.RawKeys[string(key)] = value - return -} - func (tx_store *TX_Storage) Delete(dkey DataKey) { - tx_store.RawStore(dkey.MarshalBinaryPanic(), []byte{}) + tx_store.RawKeys[string(dkey.MarshalBinaryPanic())] = []byte{} return } // this will load the variable, and if the key is found +// loads are cheaper func (tx_store *TX_Storage) Load(dkey DataKey, found_value *uint64) (value Variable) { //fmt.Printf("Loading %+v \n", dkey) @@ -104,6 +96,15 @@ func (tx_store *TX_Storage) Load(dkey DataKey, found_value *uint64) (value Varia if err := value.UnmarshalBinary(result); err != nil { panic(err) } + + if tx_store.State != nil { + if value.Length() > 10 { + tx_store.State.ConsumeStorageGas(value.Length() / 10) + } else { + tx_store.State.ConsumeStorageGas(1) + } + } + return value } @@ -112,6 +113,13 @@ func (tx_store *TX_Storage) Load(dkey DataKey, found_value *uint64) (value Varia } value = tx_store.DiskLoader(dkey, found_value) + if tx_store.State != nil { + if value.Length() > 10 { + tx_store.State.ConsumeStorageGas(value.Length() / 10) + } else { + tx_store.State.ConsumeStorageGas(1) + } + } return } @@ -120,7 +128,10 @@ func (tx_store *TX_Storage) Load(dkey DataKey, found_value *uint64) (value Varia func (tx_store *TX_Storage) Store(dkey DataKey, v Variable) { //fmt.Printf("Storing request %+v : %+v\n", dkey, v) - tx_store.RawKeys[string(dkey.MarshalBinaryPanic())] = v.MarshalBinaryPanic() + kbytes := dkey.MarshalBinaryPanic() + vbytes := v.MarshalBinaryPanic() + tx_store.State.ConsumeStorageGas(int64(len(vbytes)) * 1) + tx_store.RawKeys[string(kbytes)] = vbytes } // store variable @@ -173,6 +184,22 @@ func (dkey DataKey) MarshalBinaryPanic() (ser []byte) { return } +func (v Variable) Length() (length int64) { + switch v.Type { + case Invalid: + return + case Uint64: + var buf [binary.MaxVarintLen64]byte + done := binary.PutUvarint(buf[:], v.ValueUint64) // uint64 data type + length += int64(done) + 1 + case String: + length = int64(len([]byte(v.ValueString)) + 1) + default: + panic("unknown variable type not implemented") + } + return +} + // these are used by lowest layers func (v Variable) MarshalBinary() (data []byte, err error) { switch v.Type { diff --git a/dvm/dvm_store_memory.go b/dvm/dvm_store_memory.go deleted file mode 100644 index 480fc99..0000000 --- a/dvm/dvm_store_memory.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2017-2018 DERO Project. All rights reserved. -// Use of this source code in any form is governed by RESEARCH license. -// license can be found in the LICENSE file. -// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 -// -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package dvm - -import "fmt" - -// this file implements a RAM store backend for testing purposes - -type Memory_Storage struct { - Atoms []DataAtom // all modification operations have to played/reverse in this order - Keys map[DataKey]Variable - RawKeys map[string][]byte -} - -var Memory_Backend Memory_Storage - -func init() { - DVM_STORAGE_BACKEND = &Memory_Backend -} - -func (mem_store *Memory_Storage) RawLoad(key []byte) (value []byte, found bool) { - value, found = mem_store.RawKeys[string(key)] - return -} - -func (mem_store *Memory_Storage) RawStore(key []byte, value []byte) { - mem_store.RawKeys[string(key)] = value - return -} - -// this will load the variable, and if the key is found -func (mem_store *Memory_Storage) Load(dkey DataKey, found_value *uint64) (value Variable) { - - *found_value = 0 - // if it was modified in current TX, use it - if result, ok := mem_store.Keys[dkey]; ok { - *found_value = 1 - return result - } - - return -} - -// store variable -func (mem_store *Memory_Storage) Store(dkey DataKey, v Variable) { - - fmt.Printf("Storing %+v : %+v\n", dkey, v) - var found uint64 - old_value := mem_store.Load(dkey, &found) - - var atom DataAtom - atom.Key = dkey - atom.Value = v - if found != 0 { - atom.Prev_Value = old_value - } else { - atom.Prev_Value = Variable{} - } - - mem_store.Keys[atom.Key] = atom.Value - mem_store.Atoms = append(mem_store.Atoms, atom) - -} diff --git a/blockchain/sc.go b/dvm/sc.go similarity index 55% rename from blockchain/sc.go rename to dvm/sc.go index 4786d1c..b34661d 100644 --- a/blockchain/sc.go +++ b/dvm/sc.go @@ -14,7 +14,7 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -package blockchain +package dvm // this file implements necessary structure to SC handling @@ -24,12 +24,12 @@ import "runtime/debug" import "encoding/binary" import "github.com/deroproject/derohe/cryptography/crypto" -import "github.com/deroproject/derohe/dvm" - -//import "github.com/deroproject/graviton" +import "github.com/deroproject/derohe/config" import "github.com/deroproject/derohe/rpc" import "github.com/deroproject/derohe/globals" -import "github.com/deroproject/derohe/transaction" +import "github.com/deroproject/graviton" + +//import "github.com/deroproject/derohe/transaction" // currently DERO hash 2 contract types // 1 OPEN @@ -60,47 +60,74 @@ func SC_Meta_Key(scid crypto.Hash) []byte { return scid[:] } func SC_Code_Key(scid crypto.Hash) []byte { - return dvm.Variable{Type: dvm.String, ValueString: "C"}.MarshalBinaryPanic() + return Variable{Type: String, ValueString: "C"}.MarshalBinaryPanic() } func SC_Asset_Key(asset crypto.Hash) []byte { return asset[:] } +// used to wrap a graviton tree, so it could be discarded at any time +type Tree_Wrapper struct { + Tree *graviton.Tree + Entries map[string][]byte + Transfere []TransferExternal +} + +func (t *Tree_Wrapper) Get(key []byte) ([]byte, error) { + if value, ok := t.Entries[string(key)]; ok { + return value, nil + } else { + return t.Tree.Get(key) + } +} + +func (t *Tree_Wrapper) Put(key []byte, value []byte) error { + t.Entries[string(key)] = append([]byte{}, value...) + return nil +} + +// checks cache and returns a wrapped tree if possible +func Wrapped_tree(cache map[crypto.Hash]*graviton.Tree, ss *graviton.Snapshot, id crypto.Hash) *Tree_Wrapper { + if cached_tree, ok := cache[id]; ok { // tree is in cache return it + return &Tree_Wrapper{Tree: cached_tree, Entries: map[string][]byte{}} + } + + if tree, err := ss.GetTree(string(id[:])); err != nil { + panic(err) + } else { + return &Tree_Wrapper{Tree: tree, Entries: map[string][]byte{}} + } +} + // this will process the SC transaction // the tx should only be processed , if it has been processed -func (chain *Blockchain) execute_sc_function(w_sc_tree *Tree_Wrapper, data_tree *Tree_Wrapper, scid crypto.Hash, bl_height, bl_topoheight, bl_timestamp uint64, bl_hash crypto.Hash, tx transaction.Transaction, entrypoint string, hard_fork_version_current int64) (gas uint64, err error) { +func Execute_sc_function(w_sc_tree *Tree_Wrapper, data_tree *Tree_Wrapper, scid crypto.Hash, bl_height, bl_topoheight, bl_timestamp uint64, blid crypto.Hash, txid crypto.Hash, sc_parsed SmartContract, entrypoint string, hard_fork_version_current int64, balance_at_start uint64, signer [33]byte, incoming_value map[crypto.Hash]uint64, SCDATA rpc.Arguments, gasstorage_incoming uint64, simulator bool) (gascompute, gasstorage uint64, err error) { defer func() { if r := recover(); r != nil { // safety so if anything wrong happens, verification fails if err == nil { err = fmt.Errorf("Stack trace \n%s", debug.Stack()) } - logger.V(1).Error(err, "Recovered while rewinding chain,", "r", r, "stack trace", string(debug.Stack())) + //logger.V(1).Error(err, "Recovered while rewinding chain,", "r", r, "stack trace", string(debug.Stack())) } }() - //fmt.Printf("executing entrypoint %s\n", entrypoint) + //fmt.Printf("executing entrypoint %s values %+v feees %d\n", entrypoint, incoming_value, fees) - //if !tx.Verify_SC_Signature() { // if tx is not SC TX, or Signature could not be verified skip it - // return - //} - - tx_hash := tx.GetHash() - tx_store := dvm.Initialize_TX_store() + tx_store := Initialize_TX_store() // used as value loader from disk // this function is used to load any data required by the SC - - balance_loader := func(key dvm.DataKey) (result uint64) { + balance_loader := func(key DataKey) (result uint64) { var found bool _ = found - result, found = chain.LoadSCAssetValue(data_tree, key.SCID, key.Asset) + result, found = LoadSCAssetValue(data_tree, key.SCID, key.Asset) return result } - diskloader := func(key dvm.DataKey, found *uint64) (result dvm.Variable) { + diskloader := func(key DataKey, found *uint64) (result Variable) { var exists bool - if result, exists = chain.LoadSCValue(data_tree, key.SCID, key.MarshalBinaryPanic()); exists { + if result, exists = LoadSCValue(data_tree, key.SCID, key.MarshalBinaryPanic()); exists { *found = uint64(1) } //fmt.Printf("Loading from disk %+v result %+v found status %+v \n", key, result, exists) @@ -123,73 +150,48 @@ func (chain *Blockchain) execute_sc_function(w_sc_tree *Tree_Wrapper, data_tree return value, true } - balance, sc_parsed, found := chain.ReadSC(w_sc_tree, data_tree, scid) - if !found { - logger.V(1).Error(nil, "SC not found", "scid", scid) - err = fmt.Errorf("SC not found %s", scid) - return - } //fmt.Printf("sc_parsed %+v\n", sc_parsed) // if we found the SC in parsed form, check whether entrypoint is found function, ok := sc_parsed.Functions[entrypoint] if !ok { - logger.V(1).Error(fmt.Errorf("stored SC does not contain entrypoint"), "", "entrypoint", entrypoint, "scid", scid) err = fmt.Errorf("stored SC does not contain entrypoint '%s' scid %s \n", entrypoint, scid) return } - _ = function - - //fmt.Printf("entrypoint found '%s' scid %s\n", entrypoint, scid) - //if len(sc_tx.Params) == 0 { // initialize params if not initialized earlier - // sc_tx.Params = map[string]string{} - //} - //sc_tx.Params["value"] = fmt.Sprintf("%d", sc_tx.Value) // overide value - - tx_store.DiskLoader = diskloader // hook up loading from chain - tx_store.DiskLoaderRaw = diskloader_raw - tx_store.BalanceLoader = balance_loader - tx_store.BalanceAtStart = balance - tx_store.SCID = scid - - //fmt.Printf("tx store %v\n", tx_store) - - // we can skip proof check, here - if err = chain.Expand_Transaction_NonCoinbase(&tx); err != nil { - return - } - signer, err := extract_signer(&tx) - if err != nil { // allow anonymous SC transactions with condition that SC will not call Signer - // this allows anonymous voting and numerous other applications - // otherwise SC receives signer as all zeroes - } // setup block hash, height, topoheight correctly - state := &dvm.Shared_State{ + state := &Shared_State{ Store: tx_store, Assets: map[crypto.Hash]uint64{}, + RamStore: map[Variable]Variable{}, SCIDSELF: scid, - Chain_inputs: &dvm.Blockchain_Input{ + Chain_inputs: &Blockchain_Input{ BL_HEIGHT: bl_height, BL_TOPOHEIGHT: uint64(bl_topoheight), BL_TIMESTAMP: bl_timestamp, SCID: scid, - BLID: bl_hash, - TXID: tx_hash, + BLID: blid, + TXID: txid, Signer: string(signer[:]), }, } - if _, ok = globals.Arguments["--debug"]; ok && globals.Arguments["--debug"] != nil && chain.simulator { - state.Trace = true // enable tracing for dvm simulator + tx_store.DiskLoader = diskloader // hook up loading from chain + tx_store.DiskLoaderRaw = diskloader_raw + tx_store.BalanceLoader = balance_loader + tx_store.BalanceAtStart = balance_at_start + tx_store.SCID = scid + tx_store.State = state + if _, ok = globals.Arguments["--debug"]; ok && globals.Arguments["--debug"] != nil && simulator { + state.Trace = true // enable tracing for dvm simulator } - for _, payload := range tx.Payloads { + for asset, value := range incoming_value { var new_value [8]byte - stored_value, _ := chain.LoadSCAssetValue(data_tree, scid, payload.SCID) - binary.BigEndian.PutUint64(new_value[:], stored_value+payload.BurnValue) - chain.StoreSCValue(data_tree, scid, payload.SCID[:], new_value[:]) - state.Assets[payload.SCID] += payload.BurnValue + stored_value, _ := LoadSCAssetValue(data_tree, scid, asset) + binary.BigEndian.PutUint64(new_value[:], stored_value+value) + StoreSCValue(data_tree, scid, asset[:], new_value[:]) + state.Assets[asset] += value } // we have an entrypoint, now we must setup parameters and dvm @@ -199,16 +201,16 @@ func (chain *Blockchain) execute_sc_function(w_sc_tree *Tree_Wrapper, data_tree for _, p := range function.Params { var zerohash crypto.Hash switch { - case p.Type == dvm.Uint64 && p.Name == "value": + case p.Type == Uint64 && p.Name == "value": params[p.Name] = fmt.Sprintf("%d", state.Assets[zerohash]) // overide value - case p.Type == dvm.Uint64 && tx.SCDATA.Has(p.Name, rpc.DataUint64): - params[p.Name] = fmt.Sprintf("%d", tx.SCDATA.Value(p.Name, rpc.DataUint64).(uint64)) - case p.Type == dvm.String && tx.SCDATA.Has(p.Name, rpc.DataString): - params[p.Name] = tx.SCDATA.Value(p.Name, rpc.DataString).(string) - case p.Type == dvm.String && tx.SCDATA.Has(p.Name, rpc.DataHash): - h := tx.SCDATA.Value(p.Name, rpc.DataHash).(crypto.Hash) + case p.Type == Uint64 && SCDATA.Has(p.Name, rpc.DataUint64): + params[p.Name] = fmt.Sprintf("%d", SCDATA.Value(p.Name, rpc.DataUint64).(uint64)) + case p.Type == String && SCDATA.Has(p.Name, rpc.DataString): + params[p.Name] = SCDATA.Value(p.Name, rpc.DataString).(string) + case p.Type == String && SCDATA.Has(p.Name, rpc.DataHash): + h := SCDATA.Value(p.Name, rpc.DataHash).(crypto.Hash) params[p.Name] = string(h[:]) - fmt.Printf("%s:%x\n", p.Name, string(h[:])) + //fmt.Printf("%s:%x\n", p.Name, string(h[:])) default: err = fmt.Errorf("entrypoint '%s' parameter type missing or not yet supported (%+v)", entrypoint, p) @@ -216,22 +218,53 @@ func (chain *Blockchain) execute_sc_function(w_sc_tree *Tree_Wrapper, data_tree } } - result, err := dvm.RunSmartContract(&sc_parsed, entrypoint, state, params) + state.GasComputeLimit = int64(10000000) // everyone has fixed amount of compute gas + if state.GasComputeLimit > 0 { + state.GasComputeCheck = true + } + + // gas consumed in parameters to avoid tx bloats + if gasstorage_incoming > 0 { + if gasstorage_incoming > config.MAX_STORAGE_GAS_ATOMIC_UNITS { + gasstorage_incoming = config.MAX_STORAGE_GAS_ATOMIC_UNITS // whatever gas may be provided, upper limit of gas is this + } + + state.GasStoreLimit = int64(gasstorage_incoming) + state.GasStoreCheck = true + } + + // deduct gas from whatever has been included in TX + var scdata_bytes []byte + if scdata_bytes, err = SCDATA.MarshalBinary(); err != nil { + return + } + + scdata_length := len(scdata_bytes) + state.ConsumeStorageGas(int64(scdata_length)) + + result, err := RunSmartContract(&sc_parsed, entrypoint, state, params) + + if state.GasComputeUsed > 0 { + gascompute = uint64(state.GasComputeUsed) + } + if state.GasStoreUsed > 0 { + gasstorage = uint64(state.GasStoreUsed) + } //fmt.Printf("result value %+v\n", result) if err != nil { - logger.V(2).Error(err, "error execcuting SC", "entrypoint", entrypoint, "scid", scid) + //logger.V(2).Error(err, "error execcuting SC", "entrypoint", entrypoint, "scid", scid) return } - if err == nil && result.Type == dvm.Uint64 && result.ValueUint64 == 0 { // confirm the changes + if err == nil && result.Type == Uint64 && result.ValueUint64 == 0 { // confirm the changes for k, v := range tx_store.RawKeys { - chain.StoreSCValue(data_tree, scid, []byte(k), v) + StoreSCValue(data_tree, scid, []byte(k), v) + + // fmt.Printf("storing %x %x\n", k,v) } - - data_tree.transfere = append(data_tree.transfere, tx_store.Transfers[scid].TransferE...) - + data_tree.Transfere = append(data_tree.Transfere, tx_store.Transfers[scid].TransferE...) } else { // discard all changes, since we never write to store immediately, they are purged, however we need to return any value associated err = fmt.Errorf("Discarded knowingly") return @@ -243,7 +276,7 @@ func (chain *Blockchain) execute_sc_function(w_sc_tree *Tree_Wrapper, data_tree } // reads SC, balance -func (chain *Blockchain) ReadSC(w_sc_tree *Tree_Wrapper, data_tree *Tree_Wrapper, scid crypto.Hash) (balance uint64, sc dvm.SmartContract, found bool) { +func ReadSC(w_sc_tree *Tree_Wrapper, data_tree *Tree_Wrapper, scid crypto.Hash) (balance uint64, sc SmartContract, found bool) { meta_bytes, err := w_sc_tree.Get(SC_Meta_Key(scid)) if err != nil { return @@ -254,19 +287,19 @@ func (chain *Blockchain) ReadSC(w_sc_tree *Tree_Wrapper, data_tree *Tree_Wrapper return } var zerohash crypto.Hash - balance, _ = chain.LoadSCAssetValue(data_tree, scid, zerohash) + balance, _ = LoadSCAssetValue(data_tree, scid, zerohash) sc_bytes, err := data_tree.Get(SC_Code_Key(scid)) if err != nil { return } - var v dvm.Variable + var v Variable if err = v.UnmarshalBinary(sc_bytes); err != nil { return } - sc, pos, err := dvm.ParseSmartContract(v.ValueString) + sc, pos, err := ParseSmartContract(v.ValueString) if err != nil { return } @@ -277,7 +310,7 @@ func (chain *Blockchain) ReadSC(w_sc_tree *Tree_Wrapper, data_tree *Tree_Wrapper return } -func (chain *Blockchain) LoadSCValue(data_tree *Tree_Wrapper, scid crypto.Hash, key []byte) (v dvm.Variable, found bool) { +func LoadSCValue(data_tree *Tree_Wrapper, scid crypto.Hash, key []byte) (v Variable, found bool) { //fmt.Printf("loading fromdb %s %s \n", scid, key) object_data, err := data_tree.Get(key[:]) @@ -296,7 +329,7 @@ func (chain *Blockchain) LoadSCValue(data_tree *Tree_Wrapper, scid crypto.Hash, return v, true } -func (chain *Blockchain) LoadSCAssetValue(data_tree *Tree_Wrapper, scid crypto.Hash, asset crypto.Hash) (v uint64, found bool) { +func LoadSCAssetValue(data_tree *Tree_Wrapper, scid crypto.Hash, asset crypto.Hash) (v uint64, found bool) { //fmt.Printf("loading fromdb %s %s \n", scid, key) object_data, err := data_tree.Get(asset[:]) @@ -315,7 +348,7 @@ func (chain *Blockchain) LoadSCAssetValue(data_tree *Tree_Wrapper, scid crypto.H } // reads a value from SC, always read balance -func (chain *Blockchain) ReadSCValue(data_tree *Tree_Wrapper, scid crypto.Hash, key interface{}) (value interface{}) { +func ReadSCValue(data_tree *Tree_Wrapper, scid crypto.Hash, key interface{}) (value interface{}) { var keybytes []byte if key == nil { @@ -323,22 +356,22 @@ func (chain *Blockchain) ReadSCValue(data_tree *Tree_Wrapper, scid crypto.Hash, } switch k := key.(type) { case uint64: - keybytes = dvm.DataKey{Key: dvm.Variable{Type: dvm.Uint64, ValueUint64: k}}.MarshalBinaryPanic() + keybytes = DataKey{Key: Variable{Type: Uint64, ValueUint64: k}}.MarshalBinaryPanic() case string: - keybytes = dvm.DataKey{Key: dvm.Variable{Type: dvm.String, ValueString: k}}.MarshalBinaryPanic() + keybytes = DataKey{Key: Variable{Type: String, ValueString: k}}.MarshalBinaryPanic() //case int64: // keybytes = dvm.DataKey{Key: dvm.Variable{Type: dvm.String, Value: k}}.MarshalBinaryPanic() default: return } - value_var, found := chain.LoadSCValue(data_tree, scid, keybytes) + value_var, found := LoadSCValue(data_tree, scid, keybytes) //fmt.Printf("read value %+v", value_var) - if found && value_var.Type != dvm.Invalid { + if found && value_var.Type != Invalid { switch value_var.Type { - case dvm.Uint64: + case Uint64: value = value_var.ValueUint64 - case dvm.String: + case String: value = value_var.ValueString default: panic("This variable cannot be loaded") @@ -348,7 +381,7 @@ func (chain *Blockchain) ReadSCValue(data_tree *Tree_Wrapper, scid crypto.Hash, } // store the value in the chain -func (chain *Blockchain) StoreSCValue(data_tree *Tree_Wrapper, scid crypto.Hash, key, value []byte) { +func StoreSCValue(data_tree *Tree_Wrapper, scid crypto.Hash, key, value []byte) { if bytes.Compare(scid[:], key) == 0 { // an scid can mint its assets infinitely return } diff --git a/dvm/simulator.go b/dvm/simulator.go new file mode 100644 index 0000000..7ac6de2 --- /dev/null +++ b/dvm/simulator.go @@ -0,0 +1,382 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package dvm + +// this file implements necessary structure to SC handling + +import "fmt" + +//import "bytes" +//import "runtime/debug" +import "encoding/binary" +import "time" +import "math/big" +import "math/rand" +import "github.com/deroproject/derohe/cryptography/crypto" +import "github.com/deroproject/derohe/cryptography/bn256" + +import "golang.org/x/xerrors" +import "github.com/deroproject/graviton" +import "github.com/deroproject/derohe/rpc" +import "github.com/deroproject/derohe/config" + +//import "github.com/deroproject/derohe/transaction" + +type Simulator struct { + ss *graviton.Snapshot + balance_tree *graviton.Tree + sc_tree *graviton.Tree + cache map[crypto.Hash]*graviton.Tree + height uint64 + Balances map[string]map[string]uint64 +} + +func SimulatorInitialize(ss *graviton.Snapshot) *Simulator { + + var s Simulator + var err error + + if ss == nil { + store, err := graviton.NewMemStore() + if err != nil { + panic(err) + } + ss, err = store.LoadSnapshot(0) + if err != nil { + panic(err) + } + s.ss = ss + } + s.ss = ss + + s.balance_tree, err = ss.GetTree(config.BALANCE_TREE) + if err != nil { + panic(err) + } + s.sc_tree, err = ss.GetTree(config.SC_META) + if err != nil { + panic(err) + } + s.cache = map[crypto.Hash]*graviton.Tree{} + s.Balances = map[string]map[string]uint64{} + + //w_balance_tree := &dvm.Tree_Wrapper{Tree: balance_tree, Entries: map[string][]byte{}} + //w_sc_tree := &dvm.Tree_Wrapper{Tree: sc_tree, Entries: map[string][]byte{}} + + return &s +} + +// this is for testing some edge case in simulator +func (s *Simulator) AccountAddBalance(addr rpc.Address, scid crypto.Hash, balance_value uint64) { + balance := crypto.ConstructElGamal((*bn256.G1)(addr.PublicKey), crypto.ElGamal_BASE_G) // init zero balance + nb := crypto.NonceBalance{NonceHeight: 0, Balance: balance} + s.balance_tree.Put(addr.Compressed(), nb.Serialize()) +} + +func (s *Simulator) SCInstall(sc_code string, incoming_values map[crypto.Hash]uint64, SCDATA rpc.Arguments, signer_addr *rpc.Address, fees uint64) (scid crypto.Hash, gascompute, gasstorage uint64, err error) { + var blid crypto.Hash + rand.Seed(time.Now().Unix()) + rand.Read(scid[:]) + rand.Read(blid[:]) + + var sc SmartContract + if sc, _, err = ParseSmartContract(sc_code); err != nil { + //logger.V(2).Error(err, "error Parsing sc", "txid", txhash, "pos", pos) + return + } + + var meta SC_META_DATA + if _, ok := sc.Functions["InitializePrivate"]; ok { + meta.Type = 1 + } + + w_sc_data_tree := Wrapped_tree(s.cache, s.ss, scid) + w_sc_data_tree.Put(SC_Code_Key(scid), Variable{Type: String, ValueString: sc_code}.MarshalBinaryPanic()) + w_sc_tree := &Tree_Wrapper{Tree: s.sc_tree, Entries: map[string][]byte{}} + w_sc_tree.Put(SC_Meta_Key(scid), meta.MarshalBinary()) + + entrypoint := "Initialize" + if meta.Type == 1 { // if its a a private SC + entrypoint = "InitializePrivate" + } + + gascompute, gasstorage, err = s.common(w_sc_tree, w_sc_data_tree, scid, s.height, s.height, uint64(time.Now().UnixMilli()), blid, scid, sc, entrypoint, 1, 0, signer_addr, incoming_values, SCDATA, fees, true) + return +} + +func (s *Simulator) RunSC(incoming_values map[crypto.Hash]uint64, SCDATA rpc.Arguments, signer_addr *rpc.Address, fees uint64) (gascompute, gasstorage uint64, err error) { + var blid, txid crypto.Hash + rand.Read(blid[:]) + rand.Read(txid[:]) + + action_code := rpc.SC_ACTION(SCDATA.Value(rpc.SCACTION, rpc.DataUint64).(uint64)) + + switch action_code { + case rpc.SC_INSTALL: // request to install an SC + err = fmt.Errorf("cannot install code using this api") + return + + case rpc.SC_CALL: // trigger a CALL + if !SCDATA.Has(rpc.SCID, rpc.DataHash) { // but only if it is present + err = fmt.Errorf("no scid provided") + return + } + if !SCDATA.Has("entrypoint", rpc.DataString) { // but only if it is present + err = fmt.Errorf("no entrypoint provided") + return + } + + scid := SCDATA.Value(rpc.SCID, rpc.DataHash).(crypto.Hash) + + w_sc_tree := &Tree_Wrapper{Tree: s.sc_tree, Entries: map[string][]byte{}} + if _, err = w_sc_tree.Get(SC_Meta_Key(scid)); err != nil { + err = fmt.Errorf("scid %s not installed", scid) + return + } + + w_sc_data_tree := Wrapped_tree(s.cache, s.ss, scid) + entrypoint := SCDATA.Value("entrypoint", rpc.DataString).(string) + balance, sc, _ := ReadSC(w_sc_tree, w_sc_data_tree, scid) + + gascompute, gasstorage, err = s.common(w_sc_tree, w_sc_data_tree, scid, s.height, s.height, uint64(time.Now().UnixMilli()), blid, scid, sc, entrypoint, 1, balance, signer_addr, incoming_values, SCDATA, fees, true) + return + default: + err = fmt.Errorf("unknown action_code code %d", action_code) + } + + return +} + +func (s *Simulator) common(w_sc_tree, w_sc_data_tree *Tree_Wrapper, scid crypto.Hash, bl_height, bl_topoheight, bl_timestamp uint64, blid crypto.Hash, txid crypto.Hash, sc SmartContract, entrypoint string, hard_fork_version_current int64, balance_at_start uint64, signer_addr *rpc.Address, incoming_values map[crypto.Hash]uint64, SCDATA rpc.Arguments, fees uint64, simulator bool) (gascompute, gasstorage uint64, err error) { + + var signer [33]byte + if signer_addr != nil { + copy(signer[:], signer_addr.Compressed()) + } + + gascompute, gasstorage, err = Execute_sc_function(w_sc_tree, w_sc_data_tree, scid, bl_height, bl_topoheight, uint64(time.Now().UnixMilli()), blid, scid, sc, entrypoint, 1, 0, signer, incoming_values, SCDATA, fees, simulator) + fmt.Printf("sc execution error %s\n", err) + + // we must commit all the changes + // check whether we are not overflowing/underflowing, means SC is not over sending + if err == nil { + err = SanityCheckExternalTransfers(w_sc_data_tree, s.balance_tree, scid) + } + + if err != nil { // error occured, give everything to SC, since we may not have information to send them back + var zeroaddress [33]byte + if signer != zeroaddress { // if we can identify sender, return funds to him + ErrorRevert(s.ss, s.cache, s.balance_tree, signer, scid, incoming_values) + } else { // we could not extract signer, give burned funds to SC + ErrorRevert(s.ss, s.cache, s.balance_tree, signer, scid, incoming_values) + } + + return + } + ProcessExternal(s.ss, s.cache, s.balance_tree, signer, scid, w_sc_data_tree, w_sc_tree) + return + +} + +// this is core function used to evaluate when we are overflowing/underflowing +func SanityCheckExternalTransfers(w_sc_data_tree *Tree_Wrapper, balance_tree *graviton.Tree, scid crypto.Hash) (err error) { + total_per_asset := map[crypto.Hash]uint64{} + for _, transfer := range w_sc_data_tree.Transfere { // do external tranfer + if transfer.Amount == 0 { + continue + } + + // an SCID can generate it's token infinitely + if transfer.Asset != scid && total_per_asset[transfer.Asset]+transfer.Amount <= total_per_asset[transfer.Asset] { + err = fmt.Errorf("Balance calculation overflow") + return + } else { + total_per_asset[transfer.Asset] = total_per_asset[transfer.Asset] + transfer.Amount + } + } + + if err == nil { + for asset, value := range total_per_asset { + stored_value, _ := LoadSCAssetValue(w_sc_data_tree, scid, asset) + // an SCID can generate it's token infinitely + if asset != scid && stored_value-value > stored_value { + err = fmt.Errorf("Balance calculation underflow stored_value %d transferring %d\n", stored_value, value) + return + } + + var new_value [8]byte + binary.BigEndian.PutUint64(new_value[:], stored_value-value) + StoreSCValue(w_sc_data_tree, scid, asset[:], new_value[:]) + } + } + + //also check whether all destinations are registered + if err == nil { + for _, transfer := range w_sc_data_tree.Transfere { + if _, err = balance_tree.Get([]byte(transfer.Address)); err == nil || xerrors.Is(err, graviton.ErrNotFound) { + // everything is okay + } else { + err = fmt.Errorf("account is unregistered") + //logger.V(1).Error(err, "account is unregistered", "txhash", txhash, "scid", scid, "address", transfer.Address) + return + } + } + } + + return +} + +// any error during this will panic +func ErrorRevert(ss *graviton.Snapshot, cache map[crypto.Hash]*graviton.Tree, balance_tree *graviton.Tree, signer [33]byte, scid crypto.Hash, incoming_values map[crypto.Hash]uint64) { + var err error + + for scid_asset, burnvalue := range incoming_values { + var zeroscid crypto.Hash + + var curbtree *graviton.Tree + switch scid_asset { + case zeroscid: // main dero balance, handle it + curbtree = balance_tree + case scid: // this scid balance, handle it + curbtree = cache[scid] + default: // any other asset scid + var ok bool + if curbtree, ok = cache[scid_asset]; !ok { + if curbtree, err = ss.GetTree(string(scid_asset[:])); err != nil { + panic(err) + } + cache[scid_asset] = curbtree + } + } + + if curbtree == nil { + panic("tree cannot be nil at this point in time") + } + + if balance_serialized, err1 := curbtree.Get(signer[:]); err1 != nil { // no error can occur + panic(err1) // only disk corruption can reach here + } else { + nb := new(crypto.NonceBalance).Deserialize(balance_serialized) + nb.Balance = nb.Balance.Plus(new(big.Int).SetUint64(burnvalue)) // add back burn value to users balance homomorphically + curbtree.Put(signer[:], nb.Serialize()) // reserialize and store + } + } +} + +func ErrorDeposit(ss *graviton.Snapshot, cache map[crypto.Hash]*graviton.Tree, balance_tree *graviton.Tree, signer [33]byte, scid crypto.Hash, incoming_values map[crypto.Hash]uint64) { + + for scid_asset, burnvalue := range incoming_values { + var new_value [8]byte + + w_sc_data_tree := Wrapped_tree(cache, ss, scid) // get a new tree, discarding everything + + stored_value, _ := LoadSCAssetValue(w_sc_data_tree, scid, scid_asset) + binary.BigEndian.PutUint64(new_value[:], stored_value+burnvalue) + StoreSCValue(w_sc_data_tree, scid, scid_asset[:], new_value[:]) + + for k, v := range w_sc_data_tree.Entries { // commit incoming balances to tree + if err := w_sc_data_tree.Tree.Put([]byte(k), v); err != nil { + panic(err) + } + } + + cache[scid] = w_sc_data_tree.Tree + } +} + +func ProcessExternal(ss *graviton.Snapshot, cache map[crypto.Hash]*graviton.Tree, balance_tree *graviton.Tree, signer [33]byte, scid crypto.Hash, w_sc_data_tree, w_sc_tree *Tree_Wrapper) { + var err error + + // anything below should never give error + cache[scid] = w_sc_data_tree.Tree + + for k, v := range w_sc_data_tree.Entries { // commit entire data to tree + //if _, ok := globals.Arguments["--debug"]; ok && globals.Arguments["--debug"] != nil && chain.simulator { + // logger.V(1).Info("Writing", "txid", txhash, "scid", scid, "key", fmt.Sprintf("%x", k), "value", fmt.Sprintf("%x", v)) + // } + if len(v) == 0 { + if err = w_sc_data_tree.Tree.Delete([]byte(k)); err != nil { + panic(err) + } + } else { + if err = w_sc_data_tree.Tree.Put([]byte(k), v); err != nil { + panic(err) + } + } + } + + for k, v := range w_sc_tree.Entries { + if err = w_sc_tree.Tree.Put([]byte(k), v); err != nil { + panic(err) + } + } + + for i, transfer := range w_sc_data_tree.Transfere { // do external tranfer + if transfer.Amount == 0 { + continue + } + //fmt.Printf("%d sending to external %s %x\n", i,transfer.Asset,transfer.Address) + var zeroscid crypto.Hash + + var curbtree *graviton.Tree + switch transfer.Asset { + case zeroscid: // main dero balance, handle it + curbtree = balance_tree + case scid: // this scid balance, handle it + curbtree = cache[scid] + default: // any other asset scid + var ok bool + if curbtree, ok = cache[transfer.Asset]; !ok { + if curbtree, err = ss.GetTree(string(transfer.Asset[:])); err != nil { + panic(err) + } + cache[transfer.Asset] = curbtree + } + } + + if curbtree == nil { + panic("tree cannot be nil at this point in time") + } + + addr_bytes := []byte(transfer.Address) + if _, err = balance_tree.Get(addr_bytes); err != nil { // first check whether address is registered + err = fmt.Errorf("sending to non registered account acc %x err %s", addr_bytes, err) // this can only occur, if account no registered or dis corruption + panic(err) + } + + var balance_serialized []byte + balance_serialized, err = curbtree.Get(addr_bytes) + if err != nil && xerrors.Is(err, graviton.ErrNotFound) { // if the address is not found, lookup in main tree + var p bn256.G1 + if err = p.DecodeCompressed(addr_bytes[:]); err != nil { + panic(fmt.Errorf("key %x could not be decompressed", addr_bytes)) + } + + balance := crypto.ConstructElGamal(&p, crypto.ElGamal_BASE_G) // init zero balance + nb := crypto.NonceBalance{NonceHeight: 0, Balance: balance} + balance_serialized = nb.Serialize() + } else if err != nil { + fmt.Printf("%s %d could not transfer %d %+v\n", scid, i, transfer.Amount, addr_bytes) + panic(err) // only disk corruption can reach here + } + + nb := new(crypto.NonceBalance).Deserialize(balance_serialized) + nb.Balance = nb.Balance.Plus(new(big.Int).SetUint64(transfer.Amount)) // add transfer to users balance homomorphically + curbtree.Put(addr_bytes, nb.Serialize()) // reserialize and store + } + +} diff --git a/dvm/simulator_test.go b/dvm/simulator_test.go new file mode 100644 index 0000000..869217d --- /dev/null +++ b/dvm/simulator_test.go @@ -0,0 +1,174 @@ +// Copyright 2017-2018 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package dvm + +//import "fmt" +//import "reflect" +import "strings" +import "testing" + +import "github.com/deroproject/derohe/rpc" +import "github.com/deroproject/derohe/cryptography/crypto" + +var sc = `/* Lottery Smart Contract Example in DVM-BASIC. +This lottery smart contract will give lottery wins on every second try in following default contract. + Make depost transaction to this SCID to play lottery. + Check https://github.com/deroproject/derohe/blob/main/guide/examples/lottery_sc_guide.md +*/ + + + + Function Lottery() Uint64 + 10 dim deposit_count,winner as Uint64 + 20 LET deposit_count = LOAD("deposit_count")+1 + 25 IF DEROVALUE() == 0 THEN GOTO 110 // if deposit amount is 0, simply return + 30 STORE("depositor_address" + (deposit_count-1), SIGNER()) // store address for later on payment + 40 STORE("deposit_total", LOAD("deposit_total") + DEROVALUE() ) + 50 STORE("deposit_count",deposit_count) + 60 IF LOAD("lotteryeveryXdeposit") > deposit_count THEN GOTO 110 // we will wait till X players join in + // we are here means all players have joined in, roll the DICE, + 70 LET winner = RANDOM() % deposit_count // we have a winner + 80 SEND_DERO_TO_ADDRESS(LOAD("depositor_address" + winner) , LOAD("lotterygiveback")*LOAD("deposit_total")/10000) + // Re-Initialize for another round + 90 STORE("deposit_count", 0) // initial players + 100 STORE("deposit_total", 0) // total deposit of all players + 110 RETURN 0 + End Function + + + // This function is used to initialize parameters during install time + Function Initialize() Uint64 + 5 version("1.2.3") + 10 STORE("owner", SIGNER()) // store in DB ["owner"] = address + 20 STORE("lotteryeveryXdeposit", 2) // lottery will reward every X deposits + // How much will lottery giveback in 1/10000 parts, granularity .01 % + 30 STORE("lotterygiveback", 9900) // lottery will give reward 99% of deposits, 1 % is accumulated for owner to withdraw + 33 STORE("deposit_count", 0) // initial players + 34 STORE("deposit_total", 0) // total deposit of all players + // 35 printf "Initialize executed" + 40 RETURN 0 + End Function + + + + // Function to tune lottery parameters + Function TuneLotteryParameters(input Uint64, lotteryeveryXdeposit Uint64, lotterygiveback Uint64) Uint64 + 10 dim key,stored_owner as String + 20 dim value_uint64 as Uint64 + 30 IF LOAD("owner") == SIGNER() THEN GOTO 100 // check whether owner is real owner + 40 RETURN 1 + + 100 STORE("lotteryeveryXdeposit", lotteryeveryXdeposit) // lottery will reward every X deposits + 130 STORE("lotterygiveback", value_uint64) // how much will lottery giveback in 1/10000 parts, granularity .01 % + 140 RETURN 0 // return success + End Function + + + + // This function is used to change owner + // owner is an string form of address + Function TransferOwnership(newowner String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("tmpowner",ADDRESS_RAW(newowner)) + 40 RETURN 0 + End Function + + // Until the new owner claims ownership, existing owner remains owner + Function ClaimOwnership() Uint64 + 10 IF LOAD("tmpowner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("owner",SIGNER()) // ownership claim successful + 40 RETURN 0 + End Function + + // If signer is owner, withdraw any requested funds + // If everthing is okay, they will be showing in signers wallet + Function Withdraw( amount Uint64) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 SEND_DERO_TO_ADDRESS(SIGNER(),amount) + 40 RETURN 0 + End Function + + // If signer is owner, provide him rights to update code anytime + // make sure update is always available to SC + Function UpdateCode( code String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 UPDATE_SC_CODE(code) + 40 RETURN 0 + End Function + ` + +// run the test +func Test_Simulator_execution(t *testing.T) { + s := SimulatorInitialize(nil) + var addr *rpc.Address + var err error + + if addr, err = rpc.NewAddress(strings.TrimSpace("deto1qy0ehnqjpr0wxqnknyc66du2fsxyktppkr8m8e6jvplp954klfjz2qqdzcd8p")); err != nil { + panic(err) + } + + var zerohash crypto.Hash + + s.AccountAddBalance(*addr, zerohash, 500) + scid, gascompute, gasstorage, err := s.SCInstall(sc, map[crypto.Hash]uint64{}, rpc.Arguments{}, addr, 0) + + if err != nil { + t.Fatalf("cannot install contract %s\n", err) + } + _ = gascompute + _ = gasstorage + + // trigger first time lottery play + gascompute, gasstorage, err = s.RunSC(map[crypto.Hash]uint64{zerohash: 45}, rpc.Arguments{{rpc.SCACTION, rpc.DataUint64, uint64(rpc.SC_CALL)}, {rpc.SCID, rpc.DataHash, scid}, rpc.Argument{"entrypoint", rpc.DataString, "Lottery"}}, addr, 0) + if err != nil { + t.Fatalf("cannot run contract %s\n", err) + } + + w_sc_data_tree := Wrapped_tree(s.cache, s.ss, scid) + stored_value, _ := LoadSCAssetValue(w_sc_data_tree, scid, zerohash) + + if 45 != stored_value { + t.Fatalf("storage corruption dero value") + } + + if uint64(45) != ReadSCValue(w_sc_data_tree, scid, "deposit_total") { + t.Fatalf("storage corruption") + } + + // trigger second time lottery play + gascompute, gasstorage, err = s.RunSC(map[crypto.Hash]uint64{zerohash: 55}, rpc.Arguments{{rpc.SCACTION, rpc.DataUint64, uint64(rpc.SC_CALL)}, {rpc.SCID, rpc.DataHash, scid}, rpc.Argument{"entrypoint", rpc.DataString, "Lottery"}}, addr, 0) + if err != nil { + t.Fatalf("cannot run contract %s\n", err) + } + + w_sc_data_tree = Wrapped_tree(s.cache, s.ss, scid) + + // only 1 is left ( which is profit) + if stored_value, _ = LoadSCAssetValue(w_sc_data_tree, scid, zerohash); 1 != stored_value { + t.Fatalf("storage corruption dero value") + } + + // total deposit must be zero + if uint64(0) != ReadSCValue(w_sc_data_tree, scid, "deposit_total") { + t.Fatalf("storage corruption") + } + +} diff --git a/rpc/daemon_rpc.go b/rpc/daemon_rpc.go index 22fe5bf..b572770 100644 --- a/rpc/daemon_rpc.go +++ b/rpc/daemon_rpc.go @@ -217,6 +217,7 @@ type ( ValidBlock string `json:"valid_block"` // TX is valid in this block InvalidBlock []string `json:"invalid_block"` // TX is invalid in this block, 0 or more Ring [][]string `json:"ring"` // ring members completed, since tx contains compressed + Signer string `json:"signer"` // if signer could be extracted, it will be placed here Balance uint64 `json:"balance"` // if tx is SC, give SC balance at start Code string `json:"code"` // smart contract code at start BalanceNow uint64 `json:"balancenow"` // if tx is SC, give SC balance at current topo height @@ -269,27 +270,6 @@ type ( } ) -/* -{ - "id": "0", - "jsonrpc": "2.0", - "result": { - "alt_blocks_count": 5, - "difficulty": 972165250, - "grey_peerlist_size": 2280, - "height": 993145, - "incoming_connections_count": 0, - "outgoing_connections_count": 8, - "status": "OK", - "target": 60, - "target_height": 993137, - "testnet": false, - "top_block_hash": "", - "tx_count": 564287, - "tx_pool_size": 45, - "white_peerlist_size": 529 - } -}*/ type ( GetInfo_Params struct{} // no params GetInfo_Result struct { @@ -319,3 +299,10 @@ type ( Status string `json:"status"` } ) + +type GasEstimate_Params Transfer_Params // same structure as used by transfer call +type GasEstimate_Result struct { + GasCompute uint64 `json:"gascompute"` + GasStorage uint64 `json:"gasstorage"` + Status string `json:"status"` +} diff --git a/rpc/wallet_rpc.go b/rpc/wallet_rpc.go index 70cca3b..8a5185a 100644 --- a/rpc/wallet_rpc.go +++ b/rpc/wallet_rpc.go @@ -205,6 +205,8 @@ type ( SC_ID string `json:"scid"` SC_RPC Arguments `json:"sc_rpc"` Ringsize uint64 `json:"ringsize"` + Fees uint64 `json:"fees"` + Signer string `json:"signer"` // only used for gas estimation } Transfer_Result struct { TXID string `json:"txid,omitempty"` diff --git a/run_integration_test.sh b/run_integration_test.sh new file mode 100755 index 0000000..a2e6315 --- /dev/null +++ b/run_integration_test.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash + +CURDIR=`/bin/pwd` +BASEDIR=$(dirname $0) +ABSPATH=$(readlink -f $0) +ABSDIR=$(dirname $ABSPATH) + + +# set -x # to enable debug and verbose printing of each and every command +_DEBUG="on" +function DEBUG() +{ + [ "$_DEBUG" == "on" ] && $@ +} + + +function check_errs() +{ + # Function. Parameter 1 is the return code + # Para. 2 is text to display on failure. + + if [ "${1}" -ne "0" ]; then + echo "ERROR # ${1} : ${2}" + +# if [ "$#" -eq "3"]; then + echo "logs " + cat /dev/shm/dtest/log.txt +# cat "${3}" +# fi + # as a bonus, make our script exit with the right error code. +# rm -rf /dev/shm/dtest >/dev/null 2>&1 + killall -9 simulator >/dev/null 2>&1 + exit ${1} + fi +} + + +function run_single_test() +{ +test=${1} + killall -9 simulator >/dev/null 2>&1 + rm -rf /dev/shm/dtest >/dev/null 2>&1 + mkdir /dev/shm/dtest + cd /dev/shm/dtest + + /tmp/simulator --clog-level 2 $disableautomine >/dev/shm/dtest/dero.log 2>&1 & + disown + check_errs $? "Could not run simulator" + echo "Simulator started" + sleep 4 + + DEBUG echo "running test $test" + rm /dev/shm/dtest/log.txt >/dev/null 2>&1 + rm /dev/shm/dtest/log.txt >/dev/null 2>&1 + + cd $test + $test/run_test.sh >/dev/shm/dtest/log.txt 2>&1 + + check_errs $? "test failed $test" + killall -9 simulator >/dev/null 2>&1 + cd $CURDIR +} + +function run_tests() +{ + tests=$(find ${1} -mindepth 1 -maxdepth 1 -type d) + for test in $tests; do + run_single_test "$test" + done +} + +cd $ABSDIR +go build -o /tmp/ ./cmd/... +check_errs $? "Could not build binaries" +cd $CURDIR + + + + if [[ $# -eq 1 ]]; then # if user requsted single test + + if [ -d "$ABSDIR/tests/normal/${1}" ]; then + run_single_test "$ABSDIR/tests/normal/${1}" + exit 0 + fi + + if [ -d "$ABSDIR/tests/specific/${1}" ]; then + disableautomine="--noautomine" + run_single_test "$ABSDIR/tests/specific/${1}" + exit 0 + fi + + echo "no such test found ${1}" + exit 0 + fi + +#we will first run specific/special test cases +disableautomine="--noautomine" +run_tests $ABSDIR/tests/specific + +#we will first run normal test cases +disableautomine="" +run_tests $ABSDIR/tests/normal + diff --git a/tests/normal/gasestimate_installsc_test/run_test.sh b/tests/normal/gasestimate_installsc_test/run_test.sh new file mode 100755 index 0000000..ee39087 --- /dev/null +++ b/tests/normal/gasestimate_installsc_test/run_test.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +#set -x # to enable debug and verbose printing of each and every command + +CURDIR=`/bin/pwd` +BASEDIR=$(dirname $0) +ABSPATH=$(readlink -f $0) +ABSDIR=$(dirname $ABSPATH) + + +command -v curl >/dev/null 2>&1 || { echo "I require curl but it's not installed. Aborting." >&2; exit 1; } +command -v jq >/dev/null 2>&1 || { echo "I require jq but it's not installed. Aborting." >&2; exit 1; } +command -v base64 >/dev/null 2>&1 || { echo "I require base64 but it's not installed. Aborting." >&2; exit 1; } + + +daemon_rpc_port="20000" # daemon rpc is listening on this port + +# we have number of wallets listening at ports from 30000 +# we will be using 3 wallets, named owner, player1,player2 +owner_rpc_port="30000" +player1_rpc_port="30001" +player2_rpc_port="30002" + +owner_address=$(curl --silent http://127.0.0.1:$owner_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") +player1_address=$(curl --silent http://127.0.0.1:$player1_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") +player2_address=$(curl --silent http://127.0.0.1:$player2_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") + + +function balance(){ + curl --silent http://127.0.0.1:$1/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getbalance"}' -H 'Content-Type: application/json'| jq -r ".result.balance" +} + +function scbalance(){ + curl --silent http://127.0.0.1:$daemon_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getsc","params":{ "scid":"'"$1"'" , "code":false, "keysstring":["deposit_count"]}}' -H 'Content-Type: application/json' | jq -r ".result.balance" +} + +echo "SC owner address" $owner_address +echo "player1 address" $player1_address +echo "player2 address" $player2_address + + +# use owner wallet to load/install an lotter sc to chain +#scid=$(curl --silent --request POST --data-binary @test.bas http://127.0.0.1:$owner_rpc_port/install_sc| jq -r ".txid") +#echo "SCID" $scid +#sleep 1 + + +gas=`curl --silent http://127.0.0.1:$daemon_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getgasestimate","params":{ "transfers":[{"amount":100000,"destination":"deto1qxsz7v707t8mla4mslptlf6w7zkgrukvg5wfna0tha48yfjcahwh64qxnus7f"}], "sc":"'$(base64 -w 0 test.bas)'" }}' -H 'Content-Type: application/json' | jq -r ".result.gasstorage"` + + + + +if [[ $gas -gt 10 ]] +then + exit 0 +else + exit 1 +fi diff --git a/tests/normal/gasestimate_installsc_test/test.bas b/tests/normal/gasestimate_installsc_test/test.bas new file mode 100644 index 0000000..2b27749 --- /dev/null +++ b/tests/normal/gasestimate_installsc_test/test.bas @@ -0,0 +1,93 @@ +/* Lottery Smart Contract Example in DVM-BASIC. +This lottery smart contract will give lottery wins on every second try in following default contract. + Make depost transaction to this SCID to play lottery. + Check https://github.com/deroproject/derohe/blob/main/guide/examples/lottery_sc_guide.md +*/ + + + + Function Lottery() Uint64 + 10 dim deposit_count,winner as Uint64 + 20 LET deposit_count = LOAD("deposit_count")+1 + 25 IF DEROVALUE() == 0 THEN GOTO 110 // if deposit amount is 0, simply return + 30 STORE("depositor_address" + (deposit_count-1), SIGNER()) // store address for later on payment + 40 STORE("deposit_total", LOAD("deposit_total") + DEROVALUE() ) + 50 STORE("deposit_count",deposit_count) + 60 IF LOAD("lotteryeveryXdeposit") > deposit_count THEN GOTO 110 // we will wait till X players join in + // we are here means all players have joined in, roll the DICE, + 70 LET winner = RANDOM() % deposit_count // we have a winner + 80 SEND_DERO_TO_ADDRESS(LOAD("depositor_address" + winner) , LOAD("lotterygiveback")*LOAD("deposit_total")/10000) + // Re-Initialize for another round + 90 STORE("deposit_count", 0) // initial players + 100 STORE("deposit_total", 0) // total deposit of all players + 110 RETURN 0 + End Function + + + // This function is used to initialize parameters during install time + Function Initialize() Uint64 + 5 version("1.2.3") + 10 STORE("owner", SIGNER()) // store in DB ["owner"] = address + 20 STORE("lotteryeveryXdeposit", 2) // lottery will reward every X deposits + // How much will lottery giveback in 1/10000 parts, granularity .01 % + 30 STORE("lotterygiveback", 9900) // lottery will give reward 99% of deposits, 1 % is accumulated for owner to withdraw + 33 STORE("deposit_count", 0) // initial players + 34 STORE("deposit_total", 0) // total deposit of all players + // 35 printf "Initialize executed" + 40 RETURN 0 + End Function + + + + // Function to tune lottery parameters + Function TuneLotteryParameters(input Uint64, lotteryeveryXdeposit Uint64, lotterygiveback Uint64) Uint64 + 10 dim key,stored_owner as String + 20 dim value_uint64 as Uint64 + 30 IF LOAD("owner") == SIGNER() THEN GOTO 100 // check whether owner is real owner + 40 RETURN 1 + + 100 STORE("lotteryeveryXdeposit", lotteryeveryXdeposit) // lottery will reward every X deposits + 130 STORE("lotterygiveback", value_uint64) // how much will lottery giveback in 1/10000 parts, granularity .01 % + 140 RETURN 0 // return success + End Function + + + + // This function is used to change owner + // owner is an string form of address + Function TransferOwnership(newowner String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("tmpowner",ADDRESS_RAW(newowner)) + 40 RETURN 0 + End Function + + // Until the new owner claims ownership, existing owner remains owner + Function ClaimOwnership() Uint64 + 10 IF LOAD("tmpowner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("owner",SIGNER()) // ownership claim successful + 40 RETURN 0 + End Function + + // If signer is owner, withdraw any requested funds + // If everthing is okay, they will be showing in signers wallet + Function Withdraw( amount Uint64) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 SEND_DERO_TO_ADDRESS(SIGNER(),amount) + 40 RETURN 0 + End Function + + // If signer is owner, provide him rights to update code anytime + // make sure update is always available to SC + Function UpdateCode( code String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 UPDATE_SC_CODE(code) + 40 RETURN 0 + End Function + + + + diff --git a/tests/normal/gasestimate_runsc_test/run_test.sh b/tests/normal/gasestimate_runsc_test/run_test.sh new file mode 100755 index 0000000..048a598 --- /dev/null +++ b/tests/normal/gasestimate_runsc_test/run_test.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +#set -x # to enable debug and verbose printing of each and every command + +CURDIR=`/bin/pwd` +BASEDIR=$(dirname $0) +ABSPATH=$(readlink -f $0) +ABSDIR=$(dirname $ABSPATH) + + +command -v curl >/dev/null 2>&1 || { echo "I require curl but it's not installed. Aborting." >&2; exit 1; } +command -v jq >/dev/null 2>&1 || { echo "I require jq but it's not installed. Aborting." >&2; exit 1; } +command -v base64 >/dev/null 2>&1 || { echo "I require base64 but it's not installed. Aborting." >&2; exit 1; } + + +daemon_rpc_port="20000" # daemon rpc is listening on this port + +# we have number of wallets listening at ports from 30000 +# we will be using 3 wallets, named owner, player1,player2 +owner_rpc_port="30000" +player1_rpc_port="30001" +player2_rpc_port="30002" + +owner_address=$(curl --silent http://127.0.0.1:$owner_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") +player1_address=$(curl --silent http://127.0.0.1:$player1_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") +player2_address=$(curl --silent http://127.0.0.1:$player2_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") + + +function balance(){ + curl --silent http://127.0.0.1:$1/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getbalance"}' -H 'Content-Type: application/json'| jq -r ".result.balance" +} + +function scbalance(){ + curl --silent http://127.0.0.1:$daemon_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getsc","params":{ "scid":"'"$1"'" , "code":false, "keysstring":["deposit_count"]}}' -H 'Content-Type: application/json' | jq -r ".result.balance" +} + +echo "SC owner address" $owner_address +echo "player1 address" $player1_address +echo "player2 address" $player2_address + + +# use owner wallet to load/install an lotter sc to chain +scid=$(curl --silent --request POST --data-binary @test.bas http://127.0.0.1:$owner_rpc_port/install_sc| jq -r ".txid") +echo "SCID" $scid +sleep 3 + + +gasstorage=$(curl --silent http://127.0.0.1:$daemon_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getgasestimate","params":{ "transfers":[{"amount":100000,"destination":"deto1qxsz7v707t8mla4mslptlf6w7zkgrukvg5wfna0tha48yfjcahwh64qxnus7f"}], "sc_rpc":[{"name":"SC_ID","datatype":"H","value":"'"$scid"'"}, {"name":"SC_ACTION","datatype":"U","value":0},{"name":"entrypoint","datatype":"S","value":"Lottery"}] }}' -H 'Content-Type: application/json' | jq -r ".result.gasstorage") + + + + +if [[ $gasstorage -gt 50 ]] +then + exit 0 +else + exit 1 +fi diff --git a/tests/normal/gasestimate_runsc_test/test.bas b/tests/normal/gasestimate_runsc_test/test.bas new file mode 100644 index 0000000..2b27749 --- /dev/null +++ b/tests/normal/gasestimate_runsc_test/test.bas @@ -0,0 +1,93 @@ +/* Lottery Smart Contract Example in DVM-BASIC. +This lottery smart contract will give lottery wins on every second try in following default contract. + Make depost transaction to this SCID to play lottery. + Check https://github.com/deroproject/derohe/blob/main/guide/examples/lottery_sc_guide.md +*/ + + + + Function Lottery() Uint64 + 10 dim deposit_count,winner as Uint64 + 20 LET deposit_count = LOAD("deposit_count")+1 + 25 IF DEROVALUE() == 0 THEN GOTO 110 // if deposit amount is 0, simply return + 30 STORE("depositor_address" + (deposit_count-1), SIGNER()) // store address for later on payment + 40 STORE("deposit_total", LOAD("deposit_total") + DEROVALUE() ) + 50 STORE("deposit_count",deposit_count) + 60 IF LOAD("lotteryeveryXdeposit") > deposit_count THEN GOTO 110 // we will wait till X players join in + // we are here means all players have joined in, roll the DICE, + 70 LET winner = RANDOM() % deposit_count // we have a winner + 80 SEND_DERO_TO_ADDRESS(LOAD("depositor_address" + winner) , LOAD("lotterygiveback")*LOAD("deposit_total")/10000) + // Re-Initialize for another round + 90 STORE("deposit_count", 0) // initial players + 100 STORE("deposit_total", 0) // total deposit of all players + 110 RETURN 0 + End Function + + + // This function is used to initialize parameters during install time + Function Initialize() Uint64 + 5 version("1.2.3") + 10 STORE("owner", SIGNER()) // store in DB ["owner"] = address + 20 STORE("lotteryeveryXdeposit", 2) // lottery will reward every X deposits + // How much will lottery giveback in 1/10000 parts, granularity .01 % + 30 STORE("lotterygiveback", 9900) // lottery will give reward 99% of deposits, 1 % is accumulated for owner to withdraw + 33 STORE("deposit_count", 0) // initial players + 34 STORE("deposit_total", 0) // total deposit of all players + // 35 printf "Initialize executed" + 40 RETURN 0 + End Function + + + + // Function to tune lottery parameters + Function TuneLotteryParameters(input Uint64, lotteryeveryXdeposit Uint64, lotterygiveback Uint64) Uint64 + 10 dim key,stored_owner as String + 20 dim value_uint64 as Uint64 + 30 IF LOAD("owner") == SIGNER() THEN GOTO 100 // check whether owner is real owner + 40 RETURN 1 + + 100 STORE("lotteryeveryXdeposit", lotteryeveryXdeposit) // lottery will reward every X deposits + 130 STORE("lotterygiveback", value_uint64) // how much will lottery giveback in 1/10000 parts, granularity .01 % + 140 RETURN 0 // return success + End Function + + + + // This function is used to change owner + // owner is an string form of address + Function TransferOwnership(newowner String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("tmpowner",ADDRESS_RAW(newowner)) + 40 RETURN 0 + End Function + + // Until the new owner claims ownership, existing owner remains owner + Function ClaimOwnership() Uint64 + 10 IF LOAD("tmpowner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("owner",SIGNER()) // ownership claim successful + 40 RETURN 0 + End Function + + // If signer is owner, withdraw any requested funds + // If everthing is okay, they will be showing in signers wallet + Function Withdraw( amount Uint64) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 SEND_DERO_TO_ADDRESS(SIGNER(),amount) + 40 RETURN 0 + End Function + + // If signer is owner, provide him rights to update code anytime + // make sure update is always available to SC + Function UpdateCode( code String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 UPDATE_SC_CODE(code) + 40 RETURN 0 + End Function + + + + diff --git a/tests/normal/lottery_test/run_test.sh b/tests/normal/lottery_test/run_test.sh new file mode 100755 index 0000000..7217efa --- /dev/null +++ b/tests/normal/lottery_test/run_test.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash + +#set -x # to enable debug and verbose printing of each and every command + +CURDIR=`/bin/pwd` +BASEDIR=$(dirname $0) +ABSPATH=$(readlink -f $0) +ABSDIR=$(dirname $ABSPATH) + + +command -v curl >/dev/null 2>&1 || { echo "I require curl but it's not installed. Aborting." >&2; exit 1; } +command -v jq >/dev/null 2>&1 || { echo "I require jq but it's not installed. Aborting." >&2; exit 1; } + + +daemon_rpc_port="20000" # daemon rpc is listening on this port + +# we have number of wallets listening at ports from 30000 +# we will be using 3 wallets, named owner, player1,player2 +owner_rpc_port="30000" +player1_rpc_port="30001" +player2_rpc_port="30002" + +owner_address=$(curl --silent http://127.0.0.1:$owner_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") +player1_address=$(curl --silent http://127.0.0.1:$player1_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") +player2_address=$(curl --silent http://127.0.0.1:$player2_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") + + +function balance(){ + curl --silent http://127.0.0.1:$1/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getbalance"}' -H 'Content-Type: application/json'| jq -r ".result.balance" +} + +function scbalance(){ + curl --silent http://127.0.0.1:$daemon_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getsc","params":{ "scid":"'"$1"'" , "code":false, "keysstring":["deposit_count"]}}' -H 'Content-Type: application/json' | jq -r ".result.balance" +} + +echo "SC owner address" $owner_address +echo "player1 address" $player1_address +echo "player2 address" $player2_address + + +# use owner wallet to load/install an lotter sc to chain +scid=$(curl --silent --request POST --data-binary @test.bas http://127.0.0.1:$owner_rpc_port/install_sc| jq -r ".txid") +echo "SCID" $scid +sleep 1 + + +#curl --silent http://127.0.0.1:$daemon_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getsc","params":{ "scid":"'"$scid"'" , "code":false, "keysstring":["deposit_count"]}}' -H 'Content-Type: application/json' + +echo -n "Player 1 play txid " +curl --silent http://127.0.0.1:$player1_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"scinvoke","params":{"sc_dero_deposit":3000,"scid":"'"$scid"'","ringsize":2, "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"Lottery"}] }}' -H 'Content-Type: application/json' | jq -r ".result.txid" +echo -n "Player 2 play txid " +curl --silent http://127.0.0.1:$player2_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"scinvoke","params":{"sc_dero_deposit":3000,"scid":"'"$scid"'","ringsize":2, "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"Lottery"}] }}' -H 'Content-Type: application/json' | jq -r ".result.txid" +sleep 2 + +curl --silent http://127.0.0.1:$daemon_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getsc","params":{ "scid":"'"$scid"'" , "code":false, "keysstring":["deposit_count"]}}' -H 'Content-Type: application/json' | jq -r ".result.balance" + + +echo "SC balance" $(scbalance $scid) +echo "SC owner balance" $(balance $owner_rpc_port) +echo "SC player1 balance" $(balance $player1_rpc_port) +echo "SC player2 balance" $(balance $player2_rpc_port) + +if [[ $(balance $player1_rpc_port) -gt 800000 || $(balance $player2_rpc_port) -gt 800000 ]] +then + exit 0 +else + exit 1 +fi diff --git a/tests/normal/lottery_test/test.bas b/tests/normal/lottery_test/test.bas new file mode 100644 index 0000000..97508b5 --- /dev/null +++ b/tests/normal/lottery_test/test.bas @@ -0,0 +1,93 @@ +/* Lottery Smart Contract Example in DVM-BASIC. +This lottery smart contract will give lottery wins on every second try in following default contract. + Make depost transaction to this SCID to play lottery. + Check https://github.com/deroproject/derohe/blob/main/guide/examples/lottery_sc_guide.md +*/ + + + + Function Lottery() Uint64 + 10 dim deposit_count,winner as Uint64 + 20 LET deposit_count = LOAD("deposit_count")+1 + 25 IF DEROVALUE() == 0 THEN GOTO 110 // if deposit amount is 0, simply return + 30 STORE("depositor_address" + (deposit_count-1), SIGNER()) // store address for later on payment + 40 STORE("deposit_total", LOAD("deposit_total") + DEROVALUE() ) + 50 STORE("deposit_count",deposit_count) + 60 IF LOAD("lotteryeveryXdeposit") > deposit_count THEN GOTO 110 // we will wait till X players join in + // we are here means all players have joined in, roll the DICE, + 70 LET winner = RANDOM() % deposit_count // we have a winner + 80 SEND_DERO_TO_ADDRESS(LOAD("depositor_address" + winner) , LOAD("lotterygiveback")*LOAD("deposit_total")/10000) + // Re-Initialize for another round + 90 STORE("deposit_count", 0) // initial players + 100 STORE("deposit_total", 0) // total deposit of all players + 110 RETURN 0 + End Function + + + // This function is used to initialize parameters during install time + Function Initialize() Uint64 + 5 version("1.2.3") + 10 STORE("owner", SIGNER()) // store in DB ["owner"] = address + 20 STORE("lotteryeveryXdeposit", 2) // lottery will reward every X deposits + // How much will lottery giveback in 1/10000 parts, granularity .01 % + 30 STORE("lotterygiveback", 9900) // lottery will give reward 99% of deposits, 1 % is accumulated for owner to withdraw + 33 STORE("deposit_count", 0) // initial players + 34 STORE("deposit_total", 0) // total deposit of all players + // 35 printf "Initialize executed" + 40 RETURN 0 + End Function + + + + // Function to tune lottery parameters + Function TuneLotteryParameters(input Uint64, lotteryeveryXdeposit Uint64, lotterygiveback Uint64) Uint64 + 10 dim key,stored_owner as String + 20 dim value_uint64 as Uint64 + 30 IF LOAD("owner") == SIGNER() THEN GOTO 100 // check whether owner is real owner + 40 RETURN 1 + + 100 STORE("lotteryeveryXdeposit", lotteryeveryXdeposit) // lottery will reward every X deposits + 130 STORE("lotterygiveback", value_uint64) // how much will lottery giveback in 1/10000 parts, granularity .01 % + 140 RETURN 0 // return success + End Function + + + + // This function is used to change owner + // owner is an string form of address + Function TransferOwnership(newowner String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("tmpowner",ADDRESS_RAW(newowner)) + 40 RETURN 0 + End Function + + // Until the new owner claims ownership, existing owner remains owner + Function ClaimOwnership() Uint64 + 10 IF LOAD("tmpowner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("owner",SIGNER()) // ownership claim successful + 40 RETURN 0 + End Function + + // If signer is owner, withdraw any requested funds + // If everthing is okay, they will be showing in signers wallet + Function Withdraw( amount Uint64) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 SEND_DERO_TO_ADDRESS(SIGNER(),amount) + 40 RETURN 0 + End Function + + // If signer is owner, provide him rights to update code anytime + // make sure update is always available to SC + Function UpdateCode( code String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 UPDATE_SC_CODE(code) + 40 RETURN 0 + End Function + + + + diff --git a/tests/normal/multi_deposit_test/asset.bas b/tests/normal/multi_deposit_test/asset.bas new file mode 100644 index 0000000..1865189 --- /dev/null +++ b/tests/normal/multi_deposit_test/asset.bas @@ -0,0 +1,25 @@ +/* Private Token Smart Contract Example in DVM-BASIC. + DERO Smart Contracts Tokens privacy can be understood just like banks handle cash. Once cash is out from the bank, bank is not aware about it (who owns what value), until it is deposited back. + Smart contract only maintains supply and other necessary items to keep it working. + DERO Tokens can be tranfered to other wallets just like native DERO with Homomorphic Encryption and without involvement of issuing Smart Contracts. + Token issuing Smart Contract cannot hold/freeze/control their tokens once they are issued and sent to any wallet. + This token is Private. Use Function InitializePrivate() Uint64 to make any Smart Contract private. + +*/ + + + // Issue Asset after depositing DERO (Convert DERO to TOKENX) + Function IssueAsset() Uint64 + 10 SEND_ASSET_TO_ADDRESS(SIGNER(), DEROVALUE(),SCID()) // send asset without knowing original balance, this is done homomorphically + 20 RETURN 0 + End Function + + + // This function is used to initialize parameters during install time + // InitializePrivate initializes a private SC + Function InitializePrivate() Uint64 + 40 RETURN 0 + End Function + + + \ No newline at end of file diff --git a/tests/normal/multi_deposit_test/asset_exchange.bas b/tests/normal/multi_deposit_test/asset_exchange.bas new file mode 100644 index 0000000..5d6c6bb --- /dev/null +++ b/tests/normal/multi_deposit_test/asset_exchange.bas @@ -0,0 +1,67 @@ +// Asset Interchange/Exchnage Smart Contract Example in DVM-BASIC. +// This SC allows you to deposit an arbitrary token, into it +// and later allows you to swap one token with another +// if the SC has enough balance to cover outgoing transfer it will be done + + + // deposits an arbitrary token + // owner should deposits all arbitrary types + + Function Deposit() Uint64 + 20 RETURN 0 + End Function + +// incoming represents incoming asset type basicallly an SCID +// outgoing represents outgoing asset type basicallly an SCID + Function Interchange(incoming String, outgoing String) Uint64 + 10 SEND_ASSET_TO_ADDRESS(SIGNER(),ASSETVALUE(incoming)/2, outgoing) // 1 to 1 interchange of assets + 20 RETURN 0 + End Function + + + Function Initialize() Uint64 + 10 STORE("owner", SIGNER()) // store in DB ["owner"] = address + 40 RETURN 0 + End Function + + +// everything below this is supplementary and not required + + // This function is used to change owner + // owner is an string form of address + Function TransferOwnership(newowner String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("tmpowner",ADDRESS_RAW(newowner)) + 40 RETURN 0 + End Function + + // Until the new owner claims ownership, existing owner remains owner + Function ClaimOwnership() Uint64 + 10 IF LOAD("tmpowner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("owner",SIGNER()) // ownership claim successful + 40 RETURN 0 + End Function + + // if signer is owner, withdraw any requested funds + // if everthing is okay, they will be showing in signers wallet + Function Withdraw(amount Uint64, asset String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 SEND_ASSET_TO_ADDRESS(SIGNER(),amount,asset) + 40 RETURN 0 + End Function + + // if signer is owner, provide him rights to update code anytime + // make sure update is always available to SC + Function UpdateCode( code String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 UPDATE_SC_CODE(code) + 40 RETURN 0 + End Function + + + + diff --git a/tests/normal/multi_deposit_test/run_test.sh b/tests/normal/multi_deposit_test/run_test.sh new file mode 100755 index 0000000..c1b1734 --- /dev/null +++ b/tests/normal/multi_deposit_test/run_test.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash + +#set -x # to enable debug and verbose printing of each and every command + +# the SC will trigger panic and but cannot return the funds to sender since ringsize > 2, instead deposits to SC + +CURDIR=`/bin/pwd` +BASEDIR=$(dirname $0) +ABSPATH=$(readlink -f $0) +ABSDIR=$(dirname $ABSPATH) + +command -v curl >/dev/null 2>&1 || { echo "I require curl but it's not installed. Aborting." >&2; exit 1; } +command -v jq >/dev/null 2>&1 || { echo "I require jq but it's not installed. Aborting." >&2; exit 1; } + +baseasset="0000000000000000000000000000000000000000000000000000000000000000" + +daemon_rpc_port="20000" # daemon rpc is listening on this port + +# we have number of wallets listening at ports from 30000 +# we will be using 3 wallets, named owner, player1,player2 +owner_rpc_port="30000" +player1_rpc_port="30001" +player2_rpc_port="30002" + +owner_address=$(curl --silent http://127.0.0.1:$owner_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") +player1_address=$(curl --silent http://127.0.0.1:$player1_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") +player2_address=$(curl --silent http://127.0.0.1:$player2_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") + + +function balance(){ + curl --silent http://127.0.0.1:$1/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getbalance"}' -H 'Content-Type: application/json'| jq -r ".result.balance" +} + +function scassetbalance(){ + curl --silent http://127.0.0.1:$daemon_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getsc","params":{ "scid":"'"$1"'" , "code":false, "variables":true}}' -H 'Content-Type: application/json'| jq -r '.result.balances."'$2'"' +} + +function tokenbalance(){ + curl --silent http://127.0.0.1:$1/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getbalance" , "params":{ "scid":"'"$2"'" }}' -H 'Content-Type: application/json'| jq -r ".result.balance" +} + +owner_rpc_port="30000" +user1_rpc_port="30001" +user2_rpc_port="30002" + +owner_address=$(curl --silent http://127.0.0.1:$owner_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") +user1_address=$(curl --silent http://127.0.0.1:$user1_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") +user2_address=$(curl --silent http://127.0.0.1:$user2_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") + + +# use owner wallet to load/install an lotter sc to chain +exchangescid=$(curl --silent --request POST --data-binary @asset_exchange.bas http://127.0.0.1:$owner_rpc_port/install_sc| jq -r ".txid") +echo "exchange SCID" $exchangescid +sleep 2 + +asset1scid=$(curl --silent --request POST --data-binary @asset.bas http://127.0.0.1:$owner_rpc_port/install_sc| jq -r ".txid") +echo "asset1 SCID" $asset1scid +sleep 2 + + +asset2scid=$(curl --silent --request POST --data-binary @asset.bas http://127.0.0.1:$owner_rpc_port/install_sc| jq -r ".txid") +echo "asset2 SCID" $asset2scid +sleep 2 + + +echo -n "owner exchanging dero 1000 for asset1 " +curl --silent http://127.0.0.1:$owner_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"scinvoke","params":{"sc_dero_deposit":1000,"scid":"'"$asset1scid"'","ringsize":2, "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"IssueAsset"}] }}' -H 'Content-Type: application/json' | jq -r ".result.txid" +sleep 2 +echo -n "owner exchanging dero 1000 for asset2 " +curl --silent http://127.0.0.1:$owner_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"scinvoke","params":{"sc_dero_deposit":1000,"scid":"'"$asset2scid"'","ringsize":2, "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"IssueAsset"}] }}' -H 'Content-Type: application/json' | jq -r ".result.txid" +sleep 2 + + +echo -n "owner depositing triple assets ( dero, asset1, asset2 ) to exchangescid " +curl --silent http://127.0.0.1:$owner_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"transfer","params":{"scid":"'"$exchangescid"'","ringsize":2, "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"Deposit"}], "transfers": [{"burn":1234,"destination":"'"$user2_address"'"}, {"scid":"'"$asset1scid"'", "burn":123}, {"scid":"'"$asset2scid"'", "burn":555}] }}' -H 'Content-Type: application/json' | jq -r ".result.txid" +sleep 2 + + +echo "SC DERO balance" $(scassetbalance $exchangescid $baseasset ) +echo "SC Asset1 balance" $(scassetbalance $exchangescid $asset1scid ) +echo "SC Asset2 balance" $(scassetbalance $exchangescid $asset2scid ) + + +if [[ $(scassetbalance $exchangescid $baseasset) -ne 1234 || $(scassetbalance $exchangescid $asset1scid) -ne 123 || $(scassetbalance $exchangescid $asset2scid) -ne 555 ]] ; then + echo "condition failed" + exit 1 +fi + + +echo -n "user1 exchanging dero 2000 for asset1 " +curl --silent http://127.0.0.1:$user1_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"scinvoke","params":{"sc_dero_deposit":2000,"scid":"'"$asset1scid"'","ringsize":2, "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"IssueAsset"}] }}' -H 'Content-Type: application/json' | jq -r ".result.txid" +sleep 2 +echo "wallet1 received asset1 tokens in return" $(tokenbalance $user1_rpc_port $asset1scid) + + +echo -n "user1 depositing 122 asset1 to obtain 61 asset2" +curl --silent http://127.0.0.1:$user1_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"transfer","params":{"scid":"'"$exchangescid"'","ringsize":2, "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"Interchange"},{"name":"incoming","datatype":"H","value":"'"$asset1scid"'"},{"name":"outgoing","datatype":"H","value":"'"$asset2scid"'"}], "transfers": [{"scid":"'"$asset1scid"'", "burn":122}] }}' -H 'Content-Type: application/json' | jq -r ".result.txid" +#curl --silent http://127.0.0.1:$user1_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"scinvoke","params":{"sc_dero_deposit":10000,"scid":"'"$exchangescid"'","ringsize":2, "sc_rpc":[] }}' -H 'Content-Type: application/json' | jq -r ".result.txid" + + +sleep 2 +echo "wallet1 asset1 tokens " $(tokenbalance $user1_rpc_port $asset1scid) +echo "wallet1 asset2 tokens after exchange " $(tokenbalance $user1_rpc_port $asset2scid) + +if [[ $(tokenbalance $user1_rpc_port $asset1scid) -ne 1878 || $(tokenbalance $user1_rpc_port $asset2scid) -ne 61 ]] ; then + echo "condition failed" + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/tests/normal/panic_no_return_funds_test/run_test.sh b/tests/normal/panic_no_return_funds_test/run_test.sh new file mode 100755 index 0000000..c2678ce --- /dev/null +++ b/tests/normal/panic_no_return_funds_test/run_test.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +#set -x # to enable debug and verbose printing of each and every command + +# the SC will trigger panic and but cannot return the funds to sender since ringsize > 2, instead deposits to SC + +CURDIR=`/bin/pwd` +BASEDIR=$(dirname $0) +ABSPATH=$(readlink -f $0) +ABSDIR=$(dirname $ABSPATH) + +command -v curl >/dev/null 2>&1 || { echo "I require curl but it's not installed. Aborting." >&2; exit 1; } +command -v jq >/dev/null 2>&1 || { echo "I require jq but it's not installed. Aborting." >&2; exit 1; } + +baseasset="0000000000000000000000000000000000000000000000000000000000000000" + +daemon_rpc_port="20000" # daemon rpc is listening on this port + +# we have number of wallets listening at ports from 30000 +# we will be using 3 wallets, named owner, player1,player2 +owner_rpc_port="30000" +player1_rpc_port="30001" +player2_rpc_port="30002" + +owner_address=$(curl --silent http://127.0.0.1:$owner_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") +player1_address=$(curl --silent http://127.0.0.1:$player1_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") +player2_address=$(curl --silent http://127.0.0.1:$player2_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") + + +function balance(){ + curl --silent http://127.0.0.1:$1/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getbalance"}' -H 'Content-Type: application/json'| jq -r ".result.balance" +} + +function scassetbalance(){ + curl --silent http://127.0.0.1:$daemon_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getsc","params":{ "scid":"'"$1"'" , "code":false, "variables":true}}' -H 'Content-Type: application/json'| jq -r '.result.balances."'$2'"' +} + +echo "SC owner address" $owner_address +echo "player1 address" $player1_address +echo "player2 address" $player2_address + + +# use owner wallet to load/install an lotter sc to chain +scid=$(curl --silent --request POST --data-binary @test.bas http://127.0.0.1:$owner_rpc_port/install_sc| jq -r ".txid") +echo "SCID" $scid +sleep 2 + + +echo -n "Player 1 play txid " +curl --silent http://127.0.0.1:$player1_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"scinvoke","params":{"sc_dero_deposit":500000,"scid":"'"$scid"'","ringsize":4, "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"Lottery"}] }}' -H 'Content-Type: application/json' | jq -r ".result.txid" + +sleep 4 + +echo "SC DERO balance" $(scassetbalance $scid $baseasset ) +echo "SC owner balance" $(balance $owner_rpc_port) +echo "SC player1 balance" $(balance $player1_rpc_port) + +player1_balance=$(balance $player1_rpc_port) +if [[ $player1_balance -gt 700000 ]] ; then + exit 1 +else + exit 0 +fi diff --git a/tests/normal/panic_no_return_funds_test/test.bas b/tests/normal/panic_no_return_funds_test/test.bas new file mode 100644 index 0000000..36a8ef5 --- /dev/null +++ b/tests/normal/panic_no_return_funds_test/test.bas @@ -0,0 +1,94 @@ +/* Lottery Smart Contract Example in DVM-BASIC. +This lottery smart contract will give lottery wins on every second try in following default contract. + Make depost transaction to this SCID to play lottery. + Check https://github.com/deroproject/derohe/blob/main/guide/examples/lottery_sc_guide.md +*/ + + + + Function Lottery() Uint64 + 5 PANIC() + 10 dim deposit_count,winner as Uint64 + 20 LET deposit_count = LOAD("deposit_count")+1 + 25 IF DEROVALUE() == 0 THEN GOTO 110 // if deposit amount is 0, simply return + 30 STORE("depositor_address" + (deposit_count-1), SIGNER()) // store address for later on payment + 40 STORE("deposit_total", LOAD("deposit_total") + DEROVALUE() ) + 50 STORE("deposit_count",deposit_count) + 60 IF LOAD("lotteryeveryXdeposit") > deposit_count THEN GOTO 110 // we will wait till X players join in + // we are here means all players have joined in, roll the DICE, + 70 LET winner = RANDOM() % deposit_count // we have a winner + 80 SEND_DERO_TO_ADDRESS(LOAD("depositor_address" + winner) , LOAD("lotterygiveback")*LOAD("deposit_total")/10000) + // Re-Initialize for another round + 90 STORE("deposit_count", 0) // initial players + 100 STORE("deposit_total", 0) // total deposit of all players + 110 RETURN 0 + End Function + + + // This function is used to initialize parameters during install time + Function Initialize() Uint64 + 5 version("1.2.3") + 10 STORE("owner", SIGNER()) // store in DB ["owner"] = address + 20 STORE("lotteryeveryXdeposit", 2) // lottery will reward every X deposits + // How much will lottery giveback in 1/10000 parts, granularity .01 % + 30 STORE("lotterygiveback", 9900) // lottery will give reward 99% of deposits, 1 % is accumulated for owner to withdraw + 33 STORE("deposit_count", 0) // initial players + 34 STORE("deposit_total", 0) // total deposit of all players + // 35 printf "Initialize executed" + 40 RETURN 0 + End Function + + + + // Function to tune lottery parameters + Function TuneLotteryParameters(input Uint64, lotteryeveryXdeposit Uint64, lotterygiveback Uint64) Uint64 + 10 dim key,stored_owner as String + 20 dim value_uint64 as Uint64 + 30 IF LOAD("owner") == SIGNER() THEN GOTO 100 // check whether owner is real owner + 40 RETURN 1 + + 100 STORE("lotteryeveryXdeposit", lotteryeveryXdeposit) // lottery will reward every X deposits + 130 STORE("lotterygiveback", value_uint64) // how much will lottery giveback in 1/10000 parts, granularity .01 % + 140 RETURN 0 // return success + End Function + + + + // This function is used to change owner + // owner is an string form of address + Function TransferOwnership(newowner String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("tmpowner",ADDRESS_RAW(newowner)) + 40 RETURN 0 + End Function + + // Until the new owner claims ownership, existing owner remains owner + Function ClaimOwnership() Uint64 + 10 IF LOAD("tmpowner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("owner",SIGNER()) // ownership claim successful + 40 RETURN 0 + End Function + + // If signer is owner, withdraw any requested funds + // If everthing is okay, they will be showing in signers wallet + Function Withdraw( amount Uint64) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 SEND_DERO_TO_ADDRESS(SIGNER(),amount) + 40 RETURN 0 + End Function + + // If signer is owner, provide him rights to update code anytime + // make sure update is always available to SC + Function UpdateCode( code String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 UPDATE_SC_CODE(code) + 40 RETURN 0 + End Function + + + + diff --git a/tests/normal/panic_return_funds_test/run_test.sh b/tests/normal/panic_return_funds_test/run_test.sh new file mode 100755 index 0000000..cfcbad8 --- /dev/null +++ b/tests/normal/panic_return_funds_test/run_test.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash + +#set -x # to enable debug and verbose printing of each and every command + +# the SC will trigger panic and will return the funds +CURDIR=`/bin/pwd` +BASEDIR=$(dirname $0) +ABSPATH=$(readlink -f $0) +ABSDIR=$(dirname $ABSPATH) + +command -v curl >/dev/null 2>&1 || { echo "I require curl but it's not installed. Aborting." >&2; exit 1; } +command -v jq >/dev/null 2>&1 || { echo "I require jq but it's not installed. Aborting." >&2; exit 1; } + + +daemon_rpc_port="20000" # daemon rpc is listening on this port + +# we have number of wallets listening at ports from 30000 +# we will be using 3 wallets, named owner, player1,player2 +owner_rpc_port="30000" +player1_rpc_port="30001" +player2_rpc_port="30002" + +owner_address=$(curl --silent http://127.0.0.1:$owner_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") +player1_address=$(curl --silent http://127.0.0.1:$player1_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") +player2_address=$(curl --silent http://127.0.0.1:$player2_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") + + +function balance(){ + curl --silent http://127.0.0.1:$1/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getbalance"}' -H 'Content-Type: application/json'| jq -r ".result.balance" +} + +function scbalance(){ + curl --silent http://127.0.0.1:$daemon_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getsc","params":{ "scid":"'"$1"'" , "code":false, "keysstring":["deposit_count"]}}' -H 'Content-Type: application/json' | jq -r ".result.balance" +} + +echo "SC owner address" $owner_address +echo "player1 address" $player1_address +echo "player2 address" $player2_address + + +# use owner wallet to load/install an lotter sc to chain +scid=$(curl --silent --request POST --data-binary @test.bas http://127.0.0.1:$owner_rpc_port/install_sc| jq -r ".txid") +echo "SCID" $scid +sleep 1 + + +#curl --silent http://127.0.0.1:$daemon_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getsc","params":{ "scid":"'"$scid"'" , "code":false, "keysstring":["deposit_count"]}}' -H 'Content-Type: application/json' + +echo -n "Player 1 play txid " +curl --silent http://127.0.0.1:$player1_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"scinvoke","params":{"sc_dero_deposit":300000,"scid":"'"$scid"'","ringsize":2, "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"Lottery"}] }}' -H 'Content-Type: application/json' | jq -r ".result.txid" + + +curl --silent http://127.0.0.1:$daemon_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getsc","params":{ "scid":"'"$scid"'" , "code":false, "keysstring":["deposit_count"]}}' -H 'Content-Type: application/json' | jq -r ".result.balance" + +sleep 2 + +echo "SC balance" $(scbalance $scid) +echo "SC owner balance" $(balance $owner_rpc_port) +echo "SC player1 balance" $(balance $player1_rpc_port) + +player1_balance=$(balance $player1_rpc_port) +echo "player1 balance $player1_balance" +if [[ $player1_balance -gt 700000 ]] ; then + exit 0 +else + exit 1 +fi diff --git a/tests/normal/panic_return_funds_test/test.bas b/tests/normal/panic_return_funds_test/test.bas new file mode 100644 index 0000000..36a8ef5 --- /dev/null +++ b/tests/normal/panic_return_funds_test/test.bas @@ -0,0 +1,94 @@ +/* Lottery Smart Contract Example in DVM-BASIC. +This lottery smart contract will give lottery wins on every second try in following default contract. + Make depost transaction to this SCID to play lottery. + Check https://github.com/deroproject/derohe/blob/main/guide/examples/lottery_sc_guide.md +*/ + + + + Function Lottery() Uint64 + 5 PANIC() + 10 dim deposit_count,winner as Uint64 + 20 LET deposit_count = LOAD("deposit_count")+1 + 25 IF DEROVALUE() == 0 THEN GOTO 110 // if deposit amount is 0, simply return + 30 STORE("depositor_address" + (deposit_count-1), SIGNER()) // store address for later on payment + 40 STORE("deposit_total", LOAD("deposit_total") + DEROVALUE() ) + 50 STORE("deposit_count",deposit_count) + 60 IF LOAD("lotteryeveryXdeposit") > deposit_count THEN GOTO 110 // we will wait till X players join in + // we are here means all players have joined in, roll the DICE, + 70 LET winner = RANDOM() % deposit_count // we have a winner + 80 SEND_DERO_TO_ADDRESS(LOAD("depositor_address" + winner) , LOAD("lotterygiveback")*LOAD("deposit_total")/10000) + // Re-Initialize for another round + 90 STORE("deposit_count", 0) // initial players + 100 STORE("deposit_total", 0) // total deposit of all players + 110 RETURN 0 + End Function + + + // This function is used to initialize parameters during install time + Function Initialize() Uint64 + 5 version("1.2.3") + 10 STORE("owner", SIGNER()) // store in DB ["owner"] = address + 20 STORE("lotteryeveryXdeposit", 2) // lottery will reward every X deposits + // How much will lottery giveback in 1/10000 parts, granularity .01 % + 30 STORE("lotterygiveback", 9900) // lottery will give reward 99% of deposits, 1 % is accumulated for owner to withdraw + 33 STORE("deposit_count", 0) // initial players + 34 STORE("deposit_total", 0) // total deposit of all players + // 35 printf "Initialize executed" + 40 RETURN 0 + End Function + + + + // Function to tune lottery parameters + Function TuneLotteryParameters(input Uint64, lotteryeveryXdeposit Uint64, lotterygiveback Uint64) Uint64 + 10 dim key,stored_owner as String + 20 dim value_uint64 as Uint64 + 30 IF LOAD("owner") == SIGNER() THEN GOTO 100 // check whether owner is real owner + 40 RETURN 1 + + 100 STORE("lotteryeveryXdeposit", lotteryeveryXdeposit) // lottery will reward every X deposits + 130 STORE("lotterygiveback", value_uint64) // how much will lottery giveback in 1/10000 parts, granularity .01 % + 140 RETURN 0 // return success + End Function + + + + // This function is used to change owner + // owner is an string form of address + Function TransferOwnership(newowner String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("tmpowner",ADDRESS_RAW(newowner)) + 40 RETURN 0 + End Function + + // Until the new owner claims ownership, existing owner remains owner + Function ClaimOwnership() Uint64 + 10 IF LOAD("tmpowner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("owner",SIGNER()) // ownership claim successful + 40 RETURN 0 + End Function + + // If signer is owner, withdraw any requested funds + // If everthing is okay, they will be showing in signers wallet + Function Withdraw( amount Uint64) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 SEND_DERO_TO_ADDRESS(SIGNER(),amount) + 40 RETURN 0 + End Function + + // If signer is owner, provide him rights to update code anytime + // make sure update is always available to SC + Function UpdateCode( code String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 UPDATE_SC_CODE(code) + 40 RETURN 0 + End Function + + + + diff --git a/tests/normal/rammap_test/run_test.sh b/tests/normal/rammap_test/run_test.sh new file mode 100755 index 0000000..7217efa --- /dev/null +++ b/tests/normal/rammap_test/run_test.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash + +#set -x # to enable debug and verbose printing of each and every command + +CURDIR=`/bin/pwd` +BASEDIR=$(dirname $0) +ABSPATH=$(readlink -f $0) +ABSDIR=$(dirname $ABSPATH) + + +command -v curl >/dev/null 2>&1 || { echo "I require curl but it's not installed. Aborting." >&2; exit 1; } +command -v jq >/dev/null 2>&1 || { echo "I require jq but it's not installed. Aborting." >&2; exit 1; } + + +daemon_rpc_port="20000" # daemon rpc is listening on this port + +# we have number of wallets listening at ports from 30000 +# we will be using 3 wallets, named owner, player1,player2 +owner_rpc_port="30000" +player1_rpc_port="30001" +player2_rpc_port="30002" + +owner_address=$(curl --silent http://127.0.0.1:$owner_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") +player1_address=$(curl --silent http://127.0.0.1:$player1_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") +player2_address=$(curl --silent http://127.0.0.1:$player2_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") + + +function balance(){ + curl --silent http://127.0.0.1:$1/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getbalance"}' -H 'Content-Type: application/json'| jq -r ".result.balance" +} + +function scbalance(){ + curl --silent http://127.0.0.1:$daemon_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getsc","params":{ "scid":"'"$1"'" , "code":false, "keysstring":["deposit_count"]}}' -H 'Content-Type: application/json' | jq -r ".result.balance" +} + +echo "SC owner address" $owner_address +echo "player1 address" $player1_address +echo "player2 address" $player2_address + + +# use owner wallet to load/install an lotter sc to chain +scid=$(curl --silent --request POST --data-binary @test.bas http://127.0.0.1:$owner_rpc_port/install_sc| jq -r ".txid") +echo "SCID" $scid +sleep 1 + + +#curl --silent http://127.0.0.1:$daemon_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getsc","params":{ "scid":"'"$scid"'" , "code":false, "keysstring":["deposit_count"]}}' -H 'Content-Type: application/json' + +echo -n "Player 1 play txid " +curl --silent http://127.0.0.1:$player1_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"scinvoke","params":{"sc_dero_deposit":3000,"scid":"'"$scid"'","ringsize":2, "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"Lottery"}] }}' -H 'Content-Type: application/json' | jq -r ".result.txid" +echo -n "Player 2 play txid " +curl --silent http://127.0.0.1:$player2_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"scinvoke","params":{"sc_dero_deposit":3000,"scid":"'"$scid"'","ringsize":2, "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"Lottery"}] }}' -H 'Content-Type: application/json' | jq -r ".result.txid" +sleep 2 + +curl --silent http://127.0.0.1:$daemon_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getsc","params":{ "scid":"'"$scid"'" , "code":false, "keysstring":["deposit_count"]}}' -H 'Content-Type: application/json' | jq -r ".result.balance" + + +echo "SC balance" $(scbalance $scid) +echo "SC owner balance" $(balance $owner_rpc_port) +echo "SC player1 balance" $(balance $player1_rpc_port) +echo "SC player2 balance" $(balance $player2_rpc_port) + +if [[ $(balance $player1_rpc_port) -gt 800000 || $(balance $player2_rpc_port) -gt 800000 ]] +then + exit 0 +else + exit 1 +fi diff --git a/tests/normal/rammap_test/test.bas b/tests/normal/rammap_test/test.bas new file mode 100644 index 0000000..330b839 --- /dev/null +++ b/tests/normal/rammap_test/test.bas @@ -0,0 +1,101 @@ +/* Lottery Smart Contract Example in DVM-BASIC. +This lottery smart contract will give lottery wins on every second try in following default contract. + Make depost transaction to this SCID to play lottery. + Check https://github.com/deroproject/derohe/blob/main/guide/examples/lottery_sc_guide.md +*/ + + + + Function Lottery() Uint64 + 10 dim deposit_count,winner as Uint64 + 20 LET deposit_count = LOAD("deposit_count")+1 + 25 IF DEROVALUE() == 0 THEN GOTO 110 // if deposit amount is 0, simply return + 30 STORE("depositor_address" + (deposit_count-1), SIGNER()) // store address for later on payment + 40 STORE("deposit_total", LOAD("deposit_total") + DEROVALUE() ) + 50 STORE("deposit_count",deposit_count) + 60 IF LOAD("lotteryeveryXdeposit") > deposit_count THEN GOTO 110 // we will wait till X players join in + // we are here means all players have joined in, roll the DICE, + 70 LET winner = RANDOM() % deposit_count // we have a winner + 80 SEND_DERO_TO_ADDRESS(LOAD("depositor_address" + winner) , LOAD("lotterygiveback")*LOAD("deposit_total")/10000) + // Re-Initialize for another round + 90 STORE("deposit_count", 0) // initial players + 100 STORE("deposit_total", 0) // total deposit of all players + 110 RETURN 0 + End Function + + + function q() + 10 MAPSTORE(2, 2) // this will store to ram + 20 return + End function + // This function is used to initialize parameters during install time + Function Initialize() Uint64 + 5 MAPSTORE("owner", SIGNER()) + 10 STORE("owner", MAPGET("owner")) // store in DB ["owner"] = address + 15 q() + 20 STORE("lotteryeveryXdeposit", MAPGET(2)) //,, recover value stored in function q, lottery will reward every X deposits + // How much will lottery giveback in 1/10000 parts, granularity .01 % + 30 STORE("lotterygiveback", 9900) // lottery will give reward 99% of deposits, 1 % is accumulated for owner to withdraw + 33 STORE("deposit_count", 0) // initial players + 34 STORE("deposit_total", 0) // total deposit of all players + // 35 printf "Initialize executed" + 40 RETURN 0 + End Function + + + + + + + // Function to tune lottery parameters + Function TuneLotteryParameters(input Uint64, lotteryeveryXdeposit Uint64, lotterygiveback Uint64) Uint64 + 10 dim key,stored_owner as String + 20 dim value_uint64 as Uint64 + 30 IF LOAD("owner") == SIGNER() THEN GOTO 100 // check whether owner is real owner + 40 RETURN 1 + + 100 STORE("lotteryeveryXdeposit", lotteryeveryXdeposit) // lottery will reward every X deposits + 130 STORE("lotterygiveback", value_uint64) // how much will lottery giveback in 1/10000 parts, granularity .01 % + 140 RETURN 0 // return success + End Function + + + + // This function is used to change owner + // owner is an string form of address + Function TransferOwnership(newowner String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("tmpowner",ADDRESS_RAW(newowner)) + 40 RETURN 0 + End Function + + // Until the new owner claims ownership, existing owner remains owner + Function ClaimOwnership() Uint64 + 10 IF LOAD("tmpowner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("owner",SIGNER()) // ownership claim successful + 40 RETURN 0 + End Function + + // If signer is owner, withdraw any requested funds + // If everthing is okay, they will be showing in signers wallet + Function Withdraw( amount Uint64) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 SEND_DERO_TO_ADDRESS(SIGNER(),amount) + 40 RETURN 0 + End Function + + // If signer is owner, provide him rights to update code anytime + // make sure update is always available to SC + Function UpdateCode( code String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 UPDATE_SC_CODE(code) + 40 RETURN 0 + End Function + + + + diff --git a/tests/specific/lottery_within_block_1panic_1success_test/run_test.sh b/tests/specific/lottery_within_block_1panic_1success_test/run_test.sh new file mode 100755 index 0000000..65b2a79 --- /dev/null +++ b/tests/specific/lottery_within_block_1panic_1success_test/run_test.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash + +#set -x # to enable debug and verbose printing of each and every command + +# this will test case if lottery can run within a block + +CURDIR=`/bin/pwd` +BASEDIR=$(dirname $0) +ABSPATH=$(readlink -f $0) +ABSDIR=$(dirname $ABSPATH) + +command -v curl >/dev/null 2>&1 || { echo "I require curl but it's not installed. Aborting." >&2; exit 1; } +command -v jq >/dev/null 2>&1 || { echo "I require jq but it's not installed. Aborting." >&2; exit 1; } + +baseasset="0000000000000000000000000000000000000000000000000000000000000000" + +daemon_rpc_port="20000" # daemon rpc is listening on this port + +# we have number of wallets listening at ports from 30000 +# we will be using 3 wallets, named owner, player1,player2 +owner_rpc_port="30000" +player1_rpc_port="30001" +player2_rpc_port="30002" + +owner_address=$(curl --silent http://127.0.0.1:$owner_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") +player1_address=$(curl --silent http://127.0.0.1:$player1_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") +player2_address=$(curl --silent http://127.0.0.1:$player2_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") + + +function balance(){ + curl --silent http://127.0.0.1:$1/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getbalance"}' -H 'Content-Type: application/json'| jq -r ".result.balance" +} + +function scassetbalance(){ + curl --silent http://127.0.0.1:$daemon_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getsc","params":{ "scid":"'"$1"'" , "code":false, "variables":true}}' -H 'Content-Type: application/json'| jq -r '.result.balances."'$2'"' +} + +function mineblock(){ + touch /dev/shm/mineblocknow +} + +echo "SC owner address" $owner_address +echo "player1 address" $player1_address +echo "player2 address" $player2_address + + +# use owner wallet to load/install an lotter sc to chain +scid=$(curl --silent --request POST --data-binary @test.bas http://127.0.0.1:$owner_rpc_port/install_sc| jq -r ".txid") +echo "SCID" $scid +mineblock +sleep 2 + + +echo -n "2 players playing txid " +curl --silent http://127.0.0.1:$player1_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"scinvoke","params":{"sc_dero_deposit":500000,"scid":"'"$scid"'","ringsize":2, "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"Lottery"}] }}' -H 'Content-Type: application/json' | jq -r ".result.txid" +curl --silent http://127.0.0.1:$player2_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"scinvoke","params":{"sc_dero_deposit":400000,"scid":"'"$scid"'","ringsize":2, "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"Lottery"}] }}' -H 'Content-Type: application/json' | jq -r ".result.txid" +mineblock +sleep 4 + +echo "SC DERO balance" $(scassetbalance $scid $baseasset ) +echo "SC owner balance" $(balance $owner_rpc_port) +echo "SC player1 balance" $(balance $player1_rpc_port) +echo "SC player2 balance" $(balance $player2_rpc_port) + +if [[ $(balance $player1_rpc_port) -lt 300000 && $(balance $player2_rpc_port) -gt 790000 ]] ; then + exit 0 +else + echo "test failed" + exit 1 +fi diff --git a/tests/specific/lottery_within_block_1panic_1success_test/test.bas b/tests/specific/lottery_within_block_1panic_1success_test/test.bas new file mode 100644 index 0000000..a14941c --- /dev/null +++ b/tests/specific/lottery_within_block_1panic_1success_test/test.bas @@ -0,0 +1,96 @@ +/* Lottery Smart Contract Example in DVM-BASIC. +This lottery smart contract will give lottery wins on every second try in following default contract. + Make depost transaction to this SCID to play lottery. + Check https://github.com/deroproject/derohe/blob/main/guide/examples/lottery_sc_guide.md +*/ + + + + Function Lottery() Uint64 + 10 dim deposit_count,winner as Uint64 + 20 LET deposit_count = LOAD("deposit_count")+1 + 25 IF DEROVALUE() == 0 THEN GOTO 110 // if deposit amount is 0, simply return + 27 IF DEROVALUE() == 400000 THEN GOTO 120 // if deposit amount is 400000, panic + 30 STORE("depositor_address" + (deposit_count-1), SIGNER()) // store address for later on payment + 40 STORE("deposit_total", LOAD("deposit_total") + DEROVALUE() ) + 50 STORE("deposit_count",deposit_count) + 60 IF LOAD("lotteryeveryXdeposit") > deposit_count THEN GOTO 110 // we will wait till X players join in + // we are here means all players have joined in, roll the DICE, + 70 LET winner = RANDOM() % deposit_count // we have a winner + 80 SEND_DERO_TO_ADDRESS(LOAD("depositor_address" + winner) , LOAD("lotterygiveback")*LOAD("deposit_total")/10000) + // Re-Initialize for another round + 90 STORE("deposit_count", 0) // initial players + 100 STORE("deposit_total", 0) // total deposit of all players + 110 RETURN 0 + 120 PANIC() + 130 RETURN 0 + End Function + + + // This function is used to initialize parameters during install time + Function Initialize() Uint64 + 5 version("1.2.3") + 10 STORE("owner", SIGNER()) // store in DB ["owner"] = address + 20 STORE("lotteryeveryXdeposit", 2) // lottery will reward every X deposits + // How much will lottery giveback in 1/10000 parts, granularity .01 % + 30 STORE("lotterygiveback", 9900) // lottery will give reward 99% of deposits, 1 % is accumulated for owner to withdraw + 33 STORE("deposit_count", 0) // initial players + 34 STORE("deposit_total", 0) // total deposit of all players + // 35 printf "Initialize executed" + 40 RETURN 0 + End Function + + + + // Function to tune lottery parameters + Function TuneLotteryParameters(input Uint64, lotteryeveryXdeposit Uint64, lotterygiveback Uint64) Uint64 + 10 dim key,stored_owner as String + 20 dim value_uint64 as Uint64 + 30 IF LOAD("owner") == SIGNER() THEN GOTO 100 // check whether owner is real owner + 40 RETURN 1 + + 100 STORE("lotteryeveryXdeposit", lotteryeveryXdeposit) // lottery will reward every X deposits + 130 STORE("lotterygiveback", value_uint64) // how much will lottery giveback in 1/10000 parts, granularity .01 % + 140 RETURN 0 // return success + End Function + + + + // This function is used to change owner + // owner is an string form of address + Function TransferOwnership(newowner String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("tmpowner",ADDRESS_RAW(newowner)) + 40 RETURN 0 + End Function + + // Until the new owner claims ownership, existing owner remains owner + Function ClaimOwnership() Uint64 + 10 IF LOAD("tmpowner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("owner",SIGNER()) // ownership claim successful + 40 RETURN 0 + End Function + + // If signer is owner, withdraw any requested funds + // If everthing is okay, they will be showing in signers wallet + Function Withdraw( amount Uint64) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 SEND_DERO_TO_ADDRESS(SIGNER(),amount) + 40 RETURN 0 + End Function + + // If signer is owner, provide him rights to update code anytime + // make sure update is always available to SC + Function UpdateCode( code String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 UPDATE_SC_CODE(code) + 40 RETURN 0 + End Function + + + + diff --git a/tests/specific/lottery_within_block_test/run_test.sh b/tests/specific/lottery_within_block_test/run_test.sh new file mode 100755 index 0000000..05ed7d1 --- /dev/null +++ b/tests/specific/lottery_within_block_test/run_test.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash + +#set -x # to enable debug and verbose printing of each and every command + +# this will test case if lottery can run within a block + +CURDIR=`/bin/pwd` +BASEDIR=$(dirname $0) +ABSPATH=$(readlink -f $0) +ABSDIR=$(dirname $ABSPATH) + +command -v curl >/dev/null 2>&1 || { echo "I require curl but it's not installed. Aborting." >&2; exit 1; } +command -v jq >/dev/null 2>&1 || { echo "I require jq but it's not installed. Aborting." >&2; exit 1; } + +baseasset="0000000000000000000000000000000000000000000000000000000000000000" + +daemon_rpc_port="20000" # daemon rpc is listening on this port + +# we have number of wallets listening at ports from 30000 +# we will be using 3 wallets, named owner, player1,player2 +owner_rpc_port="30000" +player1_rpc_port="30001" +player2_rpc_port="30002" + +owner_address=$(curl --silent http://127.0.0.1:$owner_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") +player1_address=$(curl --silent http://127.0.0.1:$player1_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") +player2_address=$(curl --silent http://127.0.0.1:$player2_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json'| jq -r ".result.address") + + +function balance(){ + curl --silent http://127.0.0.1:$1/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getbalance"}' -H 'Content-Type: application/json'| jq -r ".result.balance" +} + +function scassetbalance(){ + curl --silent http://127.0.0.1:$daemon_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getsc","params":{ "scid":"'"$1"'" , "code":false, "variables":true}}' -H 'Content-Type: application/json'| jq -r '.result.balances."'$2'"' +} + +function mineblock(){ + touch /dev/shm/mineblocknow +} + +echo "SC owner address" $owner_address +echo "player1 address" $player1_address +echo "player2 address" $player2_address + + +# use owner wallet to load/install an lotter sc to chain +scid=$(curl --silent --request POST --data-binary @test.bas http://127.0.0.1:$owner_rpc_port/install_sc| jq -r ".txid") +echo "SCID" $scid +mineblock +sleep 2 + + +echo -n "2 players playing txid " +curl --silent http://127.0.0.1:$player1_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"scinvoke","params":{"sc_dero_deposit":500000,"scid":"'"$scid"'","ringsize":2, "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"Lottery"}] }}' -H 'Content-Type: application/json' | jq -r ".result.txid" +curl --silent http://127.0.0.1:$player2_rpc_port/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"scinvoke","params":{"sc_dero_deposit":500000,"scid":"'"$scid"'","ringsize":2, "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"Lottery"}] }}' -H 'Content-Type: application/json' | jq -r ".result.txid" +mineblock +sleep 4 + +echo "SC DERO balance" $(scassetbalance $scid $baseasset ) +echo "SC owner balance" $(balance $owner_rpc_port) +echo "SC player1 balance" $(balance $player1_rpc_port) +echo "SC player2 balance" $(balance $player2_rpc_port) + +if [[ $(balance $player1_rpc_port) -gt 1200000 || $(balance $player2_rpc_port) -gt 1200000 ]] +then + exit 0 +else + echo "test failed" + exit 1 +fi diff --git a/tests/specific/lottery_within_block_test/test.bas b/tests/specific/lottery_within_block_test/test.bas new file mode 100644 index 0000000..97508b5 --- /dev/null +++ b/tests/specific/lottery_within_block_test/test.bas @@ -0,0 +1,93 @@ +/* Lottery Smart Contract Example in DVM-BASIC. +This lottery smart contract will give lottery wins on every second try in following default contract. + Make depost transaction to this SCID to play lottery. + Check https://github.com/deroproject/derohe/blob/main/guide/examples/lottery_sc_guide.md +*/ + + + + Function Lottery() Uint64 + 10 dim deposit_count,winner as Uint64 + 20 LET deposit_count = LOAD("deposit_count")+1 + 25 IF DEROVALUE() == 0 THEN GOTO 110 // if deposit amount is 0, simply return + 30 STORE("depositor_address" + (deposit_count-1), SIGNER()) // store address for later on payment + 40 STORE("deposit_total", LOAD("deposit_total") + DEROVALUE() ) + 50 STORE("deposit_count",deposit_count) + 60 IF LOAD("lotteryeveryXdeposit") > deposit_count THEN GOTO 110 // we will wait till X players join in + // we are here means all players have joined in, roll the DICE, + 70 LET winner = RANDOM() % deposit_count // we have a winner + 80 SEND_DERO_TO_ADDRESS(LOAD("depositor_address" + winner) , LOAD("lotterygiveback")*LOAD("deposit_total")/10000) + // Re-Initialize for another round + 90 STORE("deposit_count", 0) // initial players + 100 STORE("deposit_total", 0) // total deposit of all players + 110 RETURN 0 + End Function + + + // This function is used to initialize parameters during install time + Function Initialize() Uint64 + 5 version("1.2.3") + 10 STORE("owner", SIGNER()) // store in DB ["owner"] = address + 20 STORE("lotteryeveryXdeposit", 2) // lottery will reward every X deposits + // How much will lottery giveback in 1/10000 parts, granularity .01 % + 30 STORE("lotterygiveback", 9900) // lottery will give reward 99% of deposits, 1 % is accumulated for owner to withdraw + 33 STORE("deposit_count", 0) // initial players + 34 STORE("deposit_total", 0) // total deposit of all players + // 35 printf "Initialize executed" + 40 RETURN 0 + End Function + + + + // Function to tune lottery parameters + Function TuneLotteryParameters(input Uint64, lotteryeveryXdeposit Uint64, lotterygiveback Uint64) Uint64 + 10 dim key,stored_owner as String + 20 dim value_uint64 as Uint64 + 30 IF LOAD("owner") == SIGNER() THEN GOTO 100 // check whether owner is real owner + 40 RETURN 1 + + 100 STORE("lotteryeveryXdeposit", lotteryeveryXdeposit) // lottery will reward every X deposits + 130 STORE("lotterygiveback", value_uint64) // how much will lottery giveback in 1/10000 parts, granularity .01 % + 140 RETURN 0 // return success + End Function + + + + // This function is used to change owner + // owner is an string form of address + Function TransferOwnership(newowner String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("tmpowner",ADDRESS_RAW(newowner)) + 40 RETURN 0 + End Function + + // Until the new owner claims ownership, existing owner remains owner + Function ClaimOwnership() Uint64 + 10 IF LOAD("tmpowner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("owner",SIGNER()) // ownership claim successful + 40 RETURN 0 + End Function + + // If signer is owner, withdraw any requested funds + // If everthing is okay, they will be showing in signers wallet + Function Withdraw( amount Uint64) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 SEND_DERO_TO_ADDRESS(SIGNER(),amount) + 40 RETURN 0 + End Function + + // If signer is owner, provide him rights to update code anytime + // make sure update is always available to SC + Function UpdateCode( code String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 UPDATE_SC_CODE(code) + 40 RETURN 0 + End Function + + + + diff --git a/transaction/transaction.go b/transaction/transaction.go index c160ddd..f56d653 100644 --- a/transaction/transaction.go +++ b/transaction/transaction.go @@ -78,7 +78,7 @@ type Transaction_Prefix struct { TransactionType TransactionType `json:"txtype"` - Value uint64 `json:"value"` // represents value for premine, SC, BURN transactions + Value uint64 `json:"value"` // represents value for premine, Gas for SC, BURN transactions MinerAddress [33]byte `json:"miner_address"` // miner address // 33 bytes also used for registration C [32]byte `json:"c"` // used for registration S [32]byte `json:"s"` // used for registration @@ -226,6 +226,27 @@ func (tx *Transaction) Fees() (fees uint64) { return fees } +// tx storage gas +func (tx *Transaction) GasStorage() (fees uint64) { + return tx.Fees() + /* + if !tx.IsSC(){ + return 0 + } + var zero_scid [32]byte + count := 0 + for i := range tx.Payloads { + if zero_scid == tx.Payloads[i].SCID { + count++ + } + } + if count == 1 { + return tx.Value + } + return 0 + */ +} + func (tx *Transaction) IsRegistrationValid() (result bool) { var u bn256.G1 @@ -293,7 +314,7 @@ func (tx *Transaction) Deserialize(buf []byte) (err error) { panic("unknown transaction type") } - if tx.TransactionType == PREMINE || tx.TransactionType == BURN_TX || tx.TransactionType == SC_TX { + if tx.TransactionType == PREMINE || tx.TransactionType == SC_TX { // represents Gas in SC tx tx.Value, done = binary.Uvarint(buf) if done <= 0 { return fmt.Errorf("Invalid Premine value in Transaction\n") @@ -420,7 +441,7 @@ func (tx *Transaction) SerializeHeader() []byte { n = binary.PutUvarint(buf, uint64(tx.TransactionType)) serialised_header.Write(buf[:n]) - if tx.TransactionType == PREMINE || tx.TransactionType == BURN_TX || tx.TransactionType == SC_TX { + if tx.TransactionType == PREMINE || tx.TransactionType == SC_TX { n := binary.PutUvarint(buf, tx.Value) serialised_header.Write(buf[:n]) } diff --git a/walletapi/rpcserver/rpc_transfer.go b/walletapi/rpcserver/rpc_transfer.go index 8002536..9a07385 100644 --- a/walletapi/rpcserver/rpc_transfer.go +++ b/walletapi/rpcserver/rpc_transfer.go @@ -64,15 +64,18 @@ func Transfer(ctx context.Context, p rpc.Transfer_Params) (result rpc.Transfer_R p.SC_RPC = append(p.SC_RPC, rpc.Argument{Name: rpc.SCID, DataType: rpc.DataHash, Value: crypto.HashHexToHash(p.SC_ID)}) } - tx, err := w.wallet.TransferPayload0(p.Transfers, p.Ringsize, false, p.SC_RPC, false) - if err != nil { - w.logger.V(1).Error(err, "Error building tx") - return result, err - } + var tx *transaction.Transaction + for tries := 0; tries < 2; tries++ { + tx, err = w.wallet.TransferPayload0(p.Transfers, p.Ringsize, false, p.SC_RPC, p.Fees, false) + if err != nil { + w.logger.V(1).Error(err, "Error building tx") + return result, err + } - err = w.wallet.SendTransaction(tx) - if err != nil { - return result, err + err = w.wallet.SendTransaction(tx) + if err == nil { + break + } } // we must return a txid if everything went alright diff --git a/walletapi/transaction_build.go b/walletapi/transaction_build.go index 4ac8195..4b18962 100644 --- a/walletapi/transaction_build.go +++ b/walletapi/transaction_build.go @@ -19,7 +19,7 @@ type GenerateProofFunc func(scid crypto.Hash, scid_index int, s *crypto.Statemen var GenerateProoffuncptr GenerateProofFunc = crypto.GenerateProof // generate proof etc -func (w *Wallet_Memory) BuildTransaction(transfers []rpc.Transfer, emap [][][]byte, rings [][]*bn256.G1, block_hash crypto.Hash, height uint64, scdata rpc.Arguments, roothash []byte, max_bits int) *transaction.Transaction { +func (w *Wallet_Memory) BuildTransaction(transfers []rpc.Transfer, emap [][][]byte, rings [][]*bn256.G1, block_hash crypto.Hash, height uint64, scdata rpc.Arguments, roothash []byte, max_bits int, fees uint64) *transaction.Transaction { sender := w.account.Keys.Public.G1() sender_secret := w.account.Keys.Secret.BigInt() @@ -34,12 +34,7 @@ rebuild_tx: tx.Height = height tx.BLID = block_hash tx.TransactionType = transaction.NORMAL - /* - if burn_value >= 1 { - tx.TransactionType = transaction.BURN_TX - tx.Value = burn_value - } - */ + if len(scdata) >= 1 { tx.TransactionType = transaction.SC_TX tx.SCDATA = scdata @@ -138,15 +133,14 @@ rebuild_tx: asset.SCID = transfers[t].SCID asset.BurnValue = transfers[t].Burn - fees := uint64(0) value := transfers[t].Amount burn_value := transfers[t].Burn - if asset.SCID.IsZero() && !fees_done { - fees = uint64(len(transfers)+2) * config.FEE_PER_KB + if fees == 0 && asset.SCID.IsZero() && !fees_done { + fees = fees + uint64(len(transfers)+2)*config.FEE_PER_KB if data, err := scdata.MarshalBinary(); err != nil { panic(err) } else { - fees = fees + uint64((len(data)/1024)+3)*config.FEE_PER_KB + fees = fees + (uint64(len(data))*15)/10 } fees_done = true } @@ -155,8 +149,14 @@ rebuild_tx: var x bn256.G1 switch { case i == witness_index[0]: - x.ScalarMult(crypto.G, new(big.Int).SetInt64(0-int64(value)-int64(fees)-int64(burn_value))) // decrease senders balance + + if asset.SCID.IsZero() { + x.ScalarMult(crypto.G, new(big.Int).SetInt64(0-int64(value)-int64(fees)-int64(burn_value))) // decrease senders balance + } else { + x.ScalarMult(crypto.G, new(big.Int).SetInt64(0-int64(value)-int64(burn_value))) // decrease senders balance + } //fmt.Printf("sender %d %s \n", i, sender.String()) + case i == witness_index[1]: x.ScalarMult(crypto.G, new(big.Int).SetInt64(int64(value))) // increase receiver's balance //fmt.Printf("receiver %d %s \n",i, receiver.String()) @@ -217,12 +217,16 @@ rebuild_tx: //fmt.Printf("t %d scid %s balance %d\n", t, transfers[t].SCID, balance) // time for bullets-sigma - statement := GenerateStatement(CLn, CRn, publickeylist, C, &D, fees) // generate statement + fees_currentasset := uint64(0) + if asset.SCID.IsZero() { + fees_currentasset = fees + } + statement := GenerateStatement(CLn, CRn, publickeylist, C, &D, fees_currentasset) // generate statement copy(statement.Roothash[:], roothash[:]) statement.Bytes_per_publickey = byte(max_bits / 8) - witness := GenerateWitness(sender_secret, r, value, balance-value-fees-burn_value, witness_index) + witness := GenerateWitness(sender_secret, r, value, balance-value-fees_currentasset-burn_value, witness_index) witness_list = append(witness_list, witness) diff --git a/walletapi/tx_creation_test.go b/walletapi/tx_creation_test.go index 8cc5067..ee6b0f4 100644 --- a/walletapi/tx_creation_test.go +++ b/walletapi/tx_creation_test.go @@ -218,7 +218,7 @@ func Test_Creation_TX(t *testing.T) { // here we are collecting proofs for later on bennhcmarking for j := 2; j <= 128; j = j * 2 { wsrc.account.Ringsize = j - tx, err := wsrc.TransferPayload0([]rpc.Transfer{rpc.Transfer{Destination: wdst.GetAddress().String(), Amount: 1}}, 0, false, rpc.Arguments{}, false) + tx, err := wsrc.TransferPayload0([]rpc.Transfer{rpc.Transfer{Destination: wdst.GetAddress().String(), Amount: 1}}, 0, false, rpc.Arguments{}, 0, false) if err != nil { t.Fatalf("Cannot create transaction, err %s", err) } else { @@ -236,7 +236,7 @@ func Test_Creation_TX(t *testing.T) { // accounts are reversed wdst.Sync_Wallet_Memory_With_Daemon() - reverse_tx, err := wdst.TransferPayload0([]rpc.Transfer{rpc.Transfer{Destination: wsrc.GetAddress().String(), Amount: 1}}, 0, false, rpc.Arguments{}, false) + reverse_tx, err := wdst.TransferPayload0([]rpc.Transfer{rpc.Transfer{Destination: wsrc.GetAddress().String(), Amount: 1}}, 0, false, rpc.Arguments{}, 0, false) if err != nil { t.Fatalf("Cannot create transaction, err %s", err) } @@ -250,7 +250,7 @@ func Test_Creation_TX(t *testing.T) { pre_transfer_src_balance := wsrc.account.Balance_Mature pre_transfer_dst_balance := wdst.account.Balance_Mature - tx, err := wsrc.TransferPayload0([]rpc.Transfer{rpc.Transfer{Destination: wdst.GetAddress().String(), Amount: 1}}, 0, false, rpc.Arguments{}, false) + tx, err := wsrc.TransferPayload0([]rpc.Transfer{rpc.Transfer{Destination: wdst.GetAddress().String(), Amount: 1}}, 0, false, rpc.Arguments{}, 0, false) if err != nil { t.Fatalf("Cannot create transaction, err %s", err) } else { @@ -292,7 +292,7 @@ func Test_Creation_TX(t *testing.T) { var tx_set []*transaction.Transaction for i := 0; i < 6; i++ { - tx, err := wsrc.TransferPayload0([]rpc.Transfer{rpc.Transfer{Destination: wdst.GetAddress().String(), Amount: 1}}, 0, false, rpc.Arguments{}, false) + tx, err := wsrc.TransferPayload0([]rpc.Transfer{rpc.Transfer{Destination: wdst.GetAddress().String(), Amount: 1}}, 0, false, rpc.Arguments{}, 0, false) if err != nil { t.Fatalf("Cannot create transaction, err %s", err) } else { diff --git a/walletapi/tx_self_creation_test.go b/walletapi/tx_self_creation_test.go index 07a10f4..1abfff7 100644 --- a/walletapi/tx_self_creation_test.go +++ b/walletapi/tx_self_creation_test.go @@ -86,6 +86,10 @@ func Test_Creation_TX_morecheck(t *testing.T) { t.Fatalf("Cannot add regtx to pool err %s", err) } + simulator_chain_mineblock(chain, wgenesis.GetAddress(), t) // mine a block at tip + simulator_chain_mineblock(chain, wgenesis.GetAddress(), t) // mine a block at tip + simulator_chain_mineblock(chain, wgenesis.GetAddress(), t) // mine a block at tip + simulator_chain_mineblock(chain, wgenesis.GetAddress(), t) // mine a block at tip simulator_chain_mineblock(chain, wgenesis.GetAddress(), t) // mine a block at tip wgenesis.SetDaemonAddress(rpcport) @@ -102,6 +106,9 @@ func Test_Creation_TX_morecheck(t *testing.T) { if err = wsrc.Sync_Wallet_Memory_With_Daemon(); err != nil { t.Fatalf("wallet sync error err %s", err) } + if err = wdst.Sync_Wallet_Memory_With_Daemon(); err != nil { + t.Fatalf("wallet sync error err %s", err) + } wsrc.account.Ringsize = 2 wdst.account.Ringsize = 2 @@ -109,7 +116,12 @@ func Test_Creation_TX_morecheck(t *testing.T) { var txs []transaction.Transaction for i := 0; i < 7; i++ { - tx, err := wsrc.TransferPayload0([]rpc.Transfer{rpc.Transfer{Destination: wdst.GetAddress().String(), Amount: 700000}}, 0, false, rpc.Arguments{}, false) + wsrc.Sync_Wallet_Memory_With_Daemon() + wdst.Sync_Wallet_Memory_With_Daemon() + + t.Logf("Chain height %d\n", chain.Get_Height()) + + tx, err := wsrc.TransferPayload0([]rpc.Transfer{rpc.Transfer{Destination: wdst.GetAddress().String(), Amount: 700000}}, 0, false, rpc.Arguments{}, 100000, false) if err != nil { t.Fatalf("Cannot create transaction, err %s", err) } else { diff --git a/walletapi/wallet_transfer.go b/walletapi/wallet_transfer.go index 88cf23d..67af1f9 100644 --- a/walletapi/wallet_transfer.go +++ b/walletapi/wallet_transfer.go @@ -57,7 +57,7 @@ func (w *Wallet_Memory) Transfer_Simplified(addr string, value uint64, data []by // we should reply to an entry // send amount to specific addresses -func (w *Wallet_Memory) TransferPayload0(transfers []rpc.Transfer, ringsize uint64, transfer_all bool, scdata rpc.Arguments, dry_run bool) (tx *transaction.Transaction, err error) { +func (w *Wallet_Memory) TransferPayload0(transfers []rpc.Transfer, ringsize uint64, transfer_all bool, scdata rpc.Arguments, gasstorage uint64, dry_run bool) (tx *transaction.Transaction, err error) { // var transfer_details structures.Outgoing_Transfer_Details w.transfer_mutex.Lock() @@ -215,6 +215,10 @@ func (w *Wallet_Memory) TransferPayload0(transfers []rpc.Transfer, ringsize uint var block_hash crypto.Hash var zeroscid crypto.Hash + + // noncetopo should be verified for all ring members simultaneously + // this can lead to tx rejection + // we currently bypass this since random members are chosen which have not been used in last 5 block _, noncetopo, block_hash, self_e, err := w.GetEncryptedBalanceAtTopoHeight(zeroscid, -1, w.GetAddress().String()) if err != nil { return @@ -362,7 +366,7 @@ func (w *Wallet_Memory) TransferPayload0(transfers []rpc.Transfer, ringsize uint max_bits += 6 // extra 6 bits if !dry_run { - tx = w.BuildTransaction(transfers, rings_balances, rings, block_hash, height, scdata, treehash_raw, max_bits) + tx = w.BuildTransaction(transfers, rings_balances, rings, block_hash, height, scdata, treehash_raw, max_bits, gasstorage) } if tx == nil {