package state import ( "bytes" "fmt" "strconv" "testing" "time" "github.com/tendermint/tendermint/account" . "github.com/tendermint/tendermint/common" dbm "github.com/tendermint/tendermint/db" "github.com/tendermint/tendermint/events" ptypes "github.com/tendermint/tendermint/permission/types" "github.com/tendermint/tendermint/types" ) /* To Test: - SendTx: x - 1 input, no perm, call perm, create perm x - 1 input, perm x - 2 inputs, one with perm one without - CallTx, CALL x - 1 input, no perm, send perm, create perm x - 1 input, perm x - contract runs call but doesn't have call perm x - contract runs call and has call perm x - contract runs call (with perm), runs contract that runs call (without perm) x - contract runs call (with perm), runs contract that runs call (with perm) - CallTx for Create, CREATE x - 1 input, no perm, send perm, call perm x - 1 input, perm x - contract runs create but doesn't have create perm x - contract runs create but has perm x - contract runs call with empty address (has call and create perm) - BondTx x - 1 input, no perm x - 1 input, perm x - 1 bonder with perm, input without send or bond x - 1 bonder with perm, input with send x - 1 bonder with perm, input with bond x - 2 inputs, one with perm one without - SendTx for new account ? CALL for new account? - Gendoug: - base: has,set,unset - roles: has, add, r */ // keys var user = makeUsers(5) func makeUsers(n int) []*account.PrivAccount { accounts := []*account.PrivAccount{} for i := 0; i < n; i++ { secret := []byte("mysecret" + strconv.Itoa(i)) user := account.GenPrivAccountFromSecret(secret) accounts = append(accounts, user) } return accounts } var ( PermsAllFalse = ptypes.NewAccountPermissions() ) func newBaseGenDoc(globalPerm, accountPerm *ptypes.AccountPermissions) GenesisDoc { genAccounts := []GenesisAccount{} for _, u := range user[:5] { genAccounts = append(genAccounts, GenesisAccount{ Address: u.Address, Amount: 1000000, Permissions: accountPerm.Copy(), }) } return GenesisDoc{ GenesisTime: time.Now(), Params: &GenesisParams{ GlobalPermissions: globalPerm, }, Accounts: genAccounts, Validators: []GenesisValidator{ GenesisValidator{ PubKey: user[0].PubKey.(account.PubKeyEd25519), Amount: 10, UnbondTo: []GenesisAccount{ GenesisAccount{ Address: user[0].Address, }, }, }, }, } } func TestSendFails(t *testing.T) { stateDB := dbm.GetDB("state") genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) genDoc.Accounts[1].Permissions.Base.Set(ptypes.Send, true) genDoc.Accounts[2].Permissions.Base.Set(ptypes.Call, true) genDoc.Accounts[3].Permissions.Base.Set(ptypes.CreateContract, true) st := MakeGenesisState(stateDB, &genDoc) blockCache := NewBlockCache(st) //------------------- // send txs // simple send tx should fail tx := types.NewSendTx() if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { t.Fatal(err) } tx.AddOutput(user[1].Address, 5) tx.SignInput(0, user[0]) if err := ExecTx(blockCache, tx, true, nil); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) } // simple send tx with call perm should fail tx = types.NewSendTx() if err := tx.AddInput(blockCache, user[2].PubKey, 5); err != nil { t.Fatal(err) } tx.AddOutput(user[4].Address, 5) tx.SignInput(0, user[2]) if err := ExecTx(blockCache, tx, true, nil); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) } // simple send tx with create perm should fail tx = types.NewSendTx() if err := tx.AddInput(blockCache, user[3].PubKey, 5); err != nil { t.Fatal(err) } tx.AddOutput(user[4].Address, 5) tx.SignInput(0, user[3]) if err := ExecTx(blockCache, tx, true, nil); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) } } func TestCallFails(t *testing.T) { stateDB := dbm.GetDB("state") genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) genDoc.Accounts[1].Permissions.Base.Set(ptypes.Send, true) genDoc.Accounts[2].Permissions.Base.Set(ptypes.Call, true) genDoc.Accounts[3].Permissions.Base.Set(ptypes.CreateContract, true) st := MakeGenesisState(stateDB, &genDoc) blockCache := NewBlockCache(st) //------------------- // call txs // simple call tx should fail tx, _ := types.NewCallTx(blockCache, user[0].PubKey, user[4].Address, nil, 100, 100, 100) tx.Sign(user[0]) if err := ExecTx(blockCache, tx, true, nil); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) } // simple call tx with send permission should fail tx, _ = types.NewCallTx(blockCache, user[1].PubKey, user[4].Address, nil, 100, 100, 100) tx.Sign(user[1]) if err := ExecTx(blockCache, tx, true, nil); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) } // simple call tx with create permission should fail tx, _ = types.NewCallTx(blockCache, user[3].PubKey, user[4].Address, nil, 100, 100, 100) tx.Sign(user[3]) if err := ExecTx(blockCache, tx, true, nil); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) } //------------------- // create txs // simple call create tx should fail tx, _ = types.NewCallTx(blockCache, user[0].PubKey, nil, nil, 100, 100, 100) tx.Sign(user[0]) if err := ExecTx(blockCache, tx, true, nil); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) } // simple call create tx with send perm should fail tx, _ = types.NewCallTx(blockCache, user[1].PubKey, nil, nil, 100, 100, 100) tx.Sign(user[1]) if err := ExecTx(blockCache, tx, true, nil); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) } // simple call create tx with call perm should fail tx, _ = types.NewCallTx(blockCache, user[2].PubKey, nil, nil, 100, 100, 100) tx.Sign(user[2]) if err := ExecTx(blockCache, tx, true, nil); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) } } func TestSendPermission(t *testing.T) { stateDB := dbm.GetDB("state") genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) genDoc.Accounts[0].Permissions.Base.Set(ptypes.Send, true) // give the 0 account permission st := MakeGenesisState(stateDB, &genDoc) blockCache := NewBlockCache(st) // A single input, having the permission, should succeed tx := types.NewSendTx() if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { t.Fatal(err) } tx.AddOutput(user[1].Address, 5) tx.SignInput(0, user[0]) if err := ExecTx(blockCache, tx, true, nil); err != nil { t.Fatal("Transaction failed", err) } // Two inputs, one with permission, one without, should fail tx = types.NewSendTx() if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { t.Fatal(err) } if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { t.Fatal(err) } tx.AddOutput(user[2].Address, 10) tx.SignInput(0, user[0]) tx.SignInput(1, user[1]) if err := ExecTx(blockCache, tx, true, nil); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) } } func TestCallPermission(t *testing.T) { stateDB := dbm.GetDB("state") genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) genDoc.Accounts[0].Permissions.Base.Set(ptypes.Call, true) // give the 0 account permission st := MakeGenesisState(stateDB, &genDoc) blockCache := NewBlockCache(st) //------------------------------ // call to simple contract fmt.Println("##### SIMPLE CONTRACT") // create simple contract simpleContractAddr := NewContractAddress(user[0].Address, 100) simpleAcc := &account.Account{ Address: simpleContractAddr, Balance: 0, Code: []byte{0x60}, Sequence: 0, StorageRoot: Zero256.Bytes(), Permissions: ptypes.NewAccountPermissions(), } st.UpdateAccount(simpleAcc) // A single input, having the permission, should succeed tx, _ := types.NewCallTx(blockCache, user[0].PubKey, simpleContractAddr, nil, 100, 100, 100) tx.Sign(user[0]) if err := ExecTx(blockCache, tx, true, nil); err != nil { t.Fatal("Transaction failed", err) } //---------------------------------------------------------- // call to contract that calls simple contract - without perm fmt.Println("##### CALL TO SIMPLE CONTRACT (FAIL)") // create contract that calls the simple contract contractCode := callContractCode(simpleContractAddr) caller1ContractAddr := NewContractAddress(user[0].Address, 101) caller1Acc := &account.Account{ Address: caller1ContractAddr, Balance: 0, Code: contractCode, Sequence: 0, StorageRoot: Zero256.Bytes(), Permissions: ptypes.NewAccountPermissions(), } blockCache.UpdateAccount(caller1Acc) // A single input, having the permission, but the contract doesn't have permission tx, _ = types.NewCallTx(blockCache, user[0].PubKey, caller1ContractAddr, nil, 100, 10000, 100) tx.Sign(user[0]) // we need to subscribe to the Receive event to detect the exception _, exception := execTxWaitEvent(t, blockCache, tx, types.EventStringAccReceive(caller1ContractAddr)) // if exception == "" { t.Fatal("Expected exception") } //---------------------------------------------------------- // call to contract that calls simple contract - with perm fmt.Println("##### CALL TO SIMPLE CONTRACT (PASS)") // A single input, having the permission, and the contract has permission caller1Acc.Permissions.Base.Set(ptypes.Call, true) blockCache.UpdateAccount(caller1Acc) tx, _ = types.NewCallTx(blockCache, user[0].PubKey, caller1ContractAddr, nil, 100, 10000, 100) tx.Sign(user[0]) // we need to subscribe to the Receive event to detect the exception _, exception = execTxWaitEvent(t, blockCache, tx, types.EventStringAccReceive(caller1ContractAddr)) // if exception != "" { t.Fatal("Unexpected exception:", exception) } //---------------------------------------------------------- // call to contract that calls contract that calls simple contract - without perm // caller1Contract calls simpleContract. caller2Contract calls caller1Contract. // caller1Contract does not have call perms, but caller2Contract does. fmt.Println("##### CALL TO CONTRACT CALLING SIMPLE CONTRACT (FAIL)") contractCode2 := callContractCode(caller1ContractAddr) caller2ContractAddr := NewContractAddress(user[0].Address, 102) caller2Acc := &account.Account{ Address: caller2ContractAddr, Balance: 1000, Code: contractCode2, Sequence: 0, StorageRoot: Zero256.Bytes(), Permissions: ptypes.NewAccountPermissions(), } caller1Acc.Permissions.Base.Set(ptypes.Call, false) caller2Acc.Permissions.Base.Set(ptypes.Call, true) blockCache.UpdateAccount(caller1Acc) blockCache.UpdateAccount(caller2Acc) tx, _ = types.NewCallTx(blockCache, user[0].PubKey, caller2ContractAddr, nil, 100, 10000, 100) tx.Sign(user[0]) // we need to subscribe to the Receive event to detect the exception _, exception = execTxWaitEvent(t, blockCache, tx, types.EventStringAccReceive(caller1ContractAddr)) // if exception == "" { t.Fatal("Expected exception") } //---------------------------------------------------------- // call to contract that calls contract that calls simple contract - without perm // caller1Contract calls simpleContract. caller2Contract calls caller1Contract. // both caller1 and caller2 have permission fmt.Println("##### CALL TO CONTRACT CALLING SIMPLE CONTRACT (PASS)") caller1Acc.Permissions.Base.Set(ptypes.Call, true) blockCache.UpdateAccount(caller1Acc) tx, _ = types.NewCallTx(blockCache, user[0].PubKey, caller2ContractAddr, nil, 100, 10000, 100) tx.Sign(user[0]) // we need to subscribe to the Receive event to detect the exception _, exception = execTxWaitEvent(t, blockCache, tx, types.EventStringAccReceive(caller1ContractAddr)) // if exception != "" { t.Fatal("Unexpected exception", exception) } } func TestCreatePermission(t *testing.T) { stateDB := dbm.GetDB("state") genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) genDoc.Accounts[0].Permissions.Base.Set(ptypes.CreateContract, true) // give the 0 account permission genDoc.Accounts[0].Permissions.Base.Set(ptypes.Call, true) // give the 0 account permission st := MakeGenesisState(stateDB, &genDoc) blockCache := NewBlockCache(st) //------------------------------ // create a simple contract fmt.Println("##### CREATE SIMPLE CONTRACT") contractCode := []byte{0x60} createCode := wrapContractForCreate(contractCode) // A single input, having the permission, should succeed tx, _ := types.NewCallTx(blockCache, user[0].PubKey, nil, createCode, 100, 100, 100) tx.Sign(user[0]) if err := ExecTx(blockCache, tx, true, nil); err != nil { t.Fatal("Transaction failed", err) } // ensure the contract is there contractAddr := NewContractAddress(tx.Input.Address, uint64(tx.Input.Sequence)) contractAcc := blockCache.GetAccount(contractAddr) if contractAcc == nil { t.Fatalf("failed to create contract %X", contractAddr) } if bytes.Compare(contractAcc.Code, contractCode) != 0 { t.Fatalf("contract does not have correct code. Got %X, expected %X", contractAcc.Code, contractCode) } //------------------------------ // create contract that uses the CREATE op fmt.Println("##### CREATE FACTORY") contractCode = []byte{0x60} createCode = wrapContractForCreate(contractCode) factoryCode := createContractCode() createFactoryCode := wrapContractForCreate(factoryCode) // A single input, having the permission, should succeed tx, _ = types.NewCallTx(blockCache, user[0].PubKey, nil, createFactoryCode, 100, 100, 100) tx.Sign(user[0]) if err := ExecTx(blockCache, tx, true, nil); err != nil { t.Fatal("Transaction failed", err) } // ensure the contract is there contractAddr = NewContractAddress(tx.Input.Address, uint64(tx.Input.Sequence)) contractAcc = blockCache.GetAccount(contractAddr) if contractAcc == nil { t.Fatalf("failed to create contract %X", contractAddr) } if bytes.Compare(contractAcc.Code, factoryCode) != 0 { t.Fatalf("contract does not have correct code. Got %X, expected %X", contractAcc.Code, factoryCode) } //------------------------------ // call the contract (should FAIL) fmt.Println("###### CALL THE FACTORY (FAIL)") // A single input, having the permission, should succeed tx, _ = types.NewCallTx(blockCache, user[0].PubKey, contractAddr, createCode, 100, 100, 100) tx.Sign(user[0]) // we need to subscribe to the Receive event to detect the exception _, exception := execTxWaitEvent(t, blockCache, tx, types.EventStringAccReceive(contractAddr)) // if exception == "" { t.Fatal("expected exception") } //------------------------------ // call the contract (should PASS) fmt.Println("###### CALL THE FACTORY (PASS)") contractAcc.Permissions.Base.Set(ptypes.CreateContract, true) blockCache.UpdateAccount(contractAcc) // A single input, having the permission, should succeed tx, _ = types.NewCallTx(blockCache, user[0].PubKey, contractAddr, createCode, 100, 100, 100) tx.Sign(user[0]) // we need to subscribe to the Receive event to detect the exception _, exception = execTxWaitEvent(t, blockCache, tx, types.EventStringAccReceive(contractAddr)) // if exception != "" { t.Fatal("unexpected exception", exception) } //-------------------------------- fmt.Println("##### CALL to empty address") zeroAddr := LeftPadBytes([]byte{}, 20) code := callContractCode(zeroAddr) contractAddr = NewContractAddress(user[0].Address, 110) contractAcc = &account.Account{ Address: contractAddr, Balance: 1000, Code: code, Sequence: 0, StorageRoot: Zero256.Bytes(), Permissions: ptypes.NewAccountPermissions(), } contractAcc.Permissions.Base.Set(ptypes.Call, true) contractAcc.Permissions.Base.Set(ptypes.CreateContract, true) blockCache.UpdateAccount(contractAcc) // this should call the 0 address but not create ... tx, _ = types.NewCallTx(blockCache, user[0].PubKey, contractAddr, createCode, 100, 10000, 100) tx.Sign(user[0]) // we need to subscribe to the Receive event to detect the exception _, exception = execTxWaitEvent(t, blockCache, tx, types.EventStringAccReceive(zeroAddr)) // if exception != "" { t.Fatal("unexpected exception", exception) } zeroAcc := blockCache.GetAccount(zeroAddr) if len(zeroAcc.Code) != 0 { t.Fatal("the zero account was given code from a CALL!") } } func TestBondPermission(t *testing.T) { stateDB := dbm.GetDB("state") genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) st := MakeGenesisState(stateDB, &genDoc) blockCache := NewBlockCache(st) var bondAcc *account.Account //------------------------------ // one bonder without permission should fail tx, _ := types.NewBondTx(user[1].PubKey) if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { t.Fatal(err) } tx.AddOutput(user[1].Address, 5) tx.SignInput(0, user[1]) tx.SignBond(user[1]) if err := ExecTx(blockCache, tx, true, nil); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) } //------------------------------ // one bonder with permission should pass bondAcc = blockCache.GetAccount(user[1].Address) bondAcc.Permissions.Base.Set(ptypes.Bond, true) blockCache.UpdateAccount(bondAcc) if err := ExecTx(blockCache, tx, true, nil); err != nil { t.Fatal("Unexpected error", err) } // reset state (we can only bond with an account once ..) genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) st = MakeGenesisState(stateDB, &genDoc) blockCache = NewBlockCache(st) bondAcc = blockCache.GetAccount(user[1].Address) bondAcc.Permissions.Base.Set(ptypes.Bond, true) blockCache.UpdateAccount(bondAcc) //------------------------------ // one bonder with permission and an input without send should fail tx, _ = types.NewBondTx(user[1].PubKey) if err := tx.AddInput(blockCache, user[2].PubKey, 5); err != nil { t.Fatal(err) } tx.AddOutput(user[1].Address, 5) tx.SignInput(0, user[2]) tx.SignBond(user[1]) if err := ExecTx(blockCache, tx, true, nil); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) } // reset state (we can only bond with an account once ..) genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) st = MakeGenesisState(stateDB, &genDoc) blockCache = NewBlockCache(st) bondAcc = blockCache.GetAccount(user[1].Address) bondAcc.Permissions.Base.Set(ptypes.Bond, true) blockCache.UpdateAccount(bondAcc) //------------------------------ // one bonder with permission and an input with send should pass sendAcc := blockCache.GetAccount(user[2].Address) sendAcc.Permissions.Base.Set(ptypes.Send, true) blockCache.UpdateAccount(sendAcc) tx, _ = types.NewBondTx(user[1].PubKey) if err := tx.AddInput(blockCache, user[2].PubKey, 5); err != nil { t.Fatal(err) } tx.AddOutput(user[1].Address, 5) tx.SignInput(0, user[2]) tx.SignBond(user[1]) if err := ExecTx(blockCache, tx, true, nil); err != nil { t.Fatal("Unexpected error", err) } // reset state (we can only bond with an account once ..) genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) st = MakeGenesisState(stateDB, &genDoc) blockCache = NewBlockCache(st) bondAcc = blockCache.GetAccount(user[1].Address) bondAcc.Permissions.Base.Set(ptypes.Bond, true) blockCache.UpdateAccount(bondAcc) //------------------------------ // one bonder with permission and an input with bond should pass sendAcc.Permissions.Base.Set(ptypes.Bond, true) blockCache.UpdateAccount(sendAcc) tx, _ = types.NewBondTx(user[1].PubKey) if err := tx.AddInput(blockCache, user[2].PubKey, 5); err != nil { t.Fatal(err) } tx.AddOutput(user[1].Address, 5) tx.SignInput(0, user[2]) tx.SignBond(user[1]) if err := ExecTx(blockCache, tx, true, nil); err != nil { t.Fatal("Unexpected error", err) } // reset state (we can only bond with an account once ..) genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) st = MakeGenesisState(stateDB, &genDoc) blockCache = NewBlockCache(st) bondAcc = blockCache.GetAccount(user[1].Address) bondAcc.Permissions.Base.Set(ptypes.Bond, true) blockCache.UpdateAccount(bondAcc) //------------------------------ // one bonder with permission and an input from that bonder and an input without send or bond should fail tx, _ = types.NewBondTx(user[1].PubKey) if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { t.Fatal(err) } if err := tx.AddInput(blockCache, user[2].PubKey, 5); err != nil { t.Fatal(err) } tx.AddOutput(user[1].Address, 5) tx.SignInput(0, user[1]) tx.SignInput(1, user[2]) tx.SignBond(user[1]) if err := ExecTx(blockCache, tx, true, nil); err == nil { t.Fatal("Expected error") } } //------------------------------------------------------------------------------------- // helpers // run ExecTx and wait for the Receive event on given addr // returns the msg data and an error/exception func execTxWaitEvent(t *testing.T, blockCache *BlockCache, tx types.Tx, eventid string) (interface{}, string) { evsw := new(events.EventSwitch) evsw.Start() ch := make(chan interface{}) evsw.AddListenerForEvent("test", eventid, func(msg interface{}) { ch <- msg }) evc := events.NewEventCache(evsw) go func() { if err := ExecTx(blockCache, tx, true, evc); err != nil { ch <- err.Error() } evc.Flush() }() msg := <-ch switch ev := msg.(type) { case types.EventMsgCallTx: return ev, ev.Exception case types.EventMsgCall: return ev, ev.Exception case string: return nil, ev default: return ev, "" } } // convenience function for contract that calls a given address func callContractCode(contractAddr []byte) []byte { // calldatacopy into mem and use as input to call memOff, inputOff := byte(0x0), byte(0x0) contractCode := []byte{0x60, memOff, 0x60, inputOff, 0x36, 0x37} gas1, gas2 := byte(0x1), byte(0x1) value := byte(0x1) inOff := byte(0x0) retOff, retSize := byte(0x0), byte(0x20) // this is the code we want to run (call a contract and return) contractCode = append(contractCode, []byte{0x60, retSize, 0x60, retOff, 0x36, 0x60, inOff, 0x60, value, 0x73}...) contractCode = append(contractCode, contractAddr...) contractCode = append(contractCode, []byte{0x61, gas1, gas2, 0xf1, 0x60, 0x20, 0x60, 0x0, 0xf3}...) return contractCode } // convenience function for contract that is a factory for the code that comes as call data func createContractCode() []byte { // TODO: gas ... // calldatacopy the calldatasize memOff, inputOff := byte(0x0), byte(0x0) contractCode := []byte{0x60, memOff, 0x60, inputOff, 0x36, 0x37} // create value := byte(0x1) contractCode = append(contractCode, []byte{0x60, value, 0x36, 0x60, memOff, 0xf0}...) return contractCode } // wrap a contract in create code func wrapContractForCreate(contractCode []byte) []byte { // the is the code we need to return the contractCode when the contract is initialized lenCode := len(contractCode) // push code to the stack code := append([]byte{0x7f}, RightPadWord256(contractCode).Bytes()...) // store it in memory code = append(code, []byte{0x60, 0x0, 0x52}...) // return whats in memory code = append(code, []byte{0x60, byte(lenCode), 0x60, 0x0, 0xf3}...) // return init code, contract code, expected return return code }