628 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			628 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2018 The go-ethereum Authors
 | |
| // This file is part of the go-ethereum library.
 | |
| //
 | |
| // The go-ethereum library is free software: you can redistribute it and/or modify
 | |
| // it under the terms of the GNU Lesser General Public License as published by
 | |
| // the Free Software Foundation, either version 3 of the License, or
 | |
| // (at your option) any later version.
 | |
| //
 | |
| // The go-ethereum library is distributed in the hope that it will be useful,
 | |
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | |
| // GNU Lesser General Public License for more details.
 | |
| //
 | |
| // You should have received a copy of the GNU Lesser General Public License
 | |
| // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
 | |
| 
 | |
| package rules
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"math/big"
 | |
| 	"strings"
 | |
| 	"testing"
 | |
| 
 | |
| 	"github.com/ethereum/go-ethereum/accounts"
 | |
| 	"github.com/ethereum/go-ethereum/common"
 | |
| 	"github.com/ethereum/go-ethereum/common/hexutil"
 | |
| 	"github.com/ethereum/go-ethereum/core/types"
 | |
| 	"github.com/ethereum/go-ethereum/internal/ethapi"
 | |
| 	"github.com/ethereum/go-ethereum/signer/core"
 | |
| 	"github.com/ethereum/go-ethereum/signer/storage"
 | |
| )
 | |
| 
 | |
| const JS = `
 | |
| /**
 | |
| This is an example implementation of a Javascript rule file.
 | |
| 
 | |
| When the signer receives a request over the external API, the corresponding method is evaluated.
 | |
| Three things can happen:
 | |
| 
 | |
| 1. The method returns "Approve". This means the operation is permitted.
 | |
| 2. The method returns "Reject". This means the operation is rejected.
 | |
| 3. Anything else; other return values [*], method not implemented or exception occurred during processing. This means
 | |
| that the operation will continue to manual processing, via the regular UI method chosen by the user.
 | |
| 
 | |
| [*] Note: Future version of the ruleset may use more complex json-based returnvalues, making it possible to not
 | |
| only respond Approve/Reject/Manual, but also modify responses. For example, choose to list only one, but not all
 | |
| accounts in a list-request. The points above will continue to hold for non-json based responses ("Approve"/"Reject").
 | |
| 
 | |
| **/
 | |
| 
 | |
| function ApproveListing(request){
 | |
| 	console.log("In js approve listing");
 | |
| 	console.log(request.accounts[3].Address)
 | |
| 	console.log(request.meta.Remote)
 | |
| 	return "Approve"
 | |
| }
 | |
| 
 | |
| function ApproveTx(request){
 | |
| 	console.log("test");
 | |
| 	console.log("from");
 | |
| 	return "Reject";
 | |
| }
 | |
| 
 | |
| function test(thing){
 | |
| 	console.log(thing.String())
 | |
| }
 | |
| 
 | |
| `
 | |
| 
 | |
| func mixAddr(a string) (*common.MixedcaseAddress, error) {
 | |
| 	return common.NewMixedcaseAddressFromString(a)
 | |
| }
 | |
| 
 | |
| type alwaysDenyUI struct{}
 | |
| 
 | |
| func (alwaysDenyUI) OnInputRequired(info core.UserInputRequest) (core.UserInputResponse, error) {
 | |
| 	return core.UserInputResponse{}, nil
 | |
| }
 | |
| func (alwaysDenyUI) RegisterUIServer(api *core.UIServerAPI) {
 | |
| }
 | |
| 
 | |
| func (alwaysDenyUI) OnSignerStartup(info core.StartupInfo) {
 | |
| }
 | |
| 
 | |
| func (alwaysDenyUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
 | |
| 	return core.SignTxResponse{Transaction: request.Transaction, Approved: false}, nil
 | |
| }
 | |
| 
 | |
| func (alwaysDenyUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
 | |
| 	return core.SignDataResponse{Approved: false}, nil
 | |
| }
 | |
| 
 | |
| func (alwaysDenyUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
 | |
| 	return core.ListResponse{Accounts: nil}, nil
 | |
| }
 | |
| 
 | |
| func (alwaysDenyUI) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
 | |
| 	return core.NewAccountResponse{Approved: false}, nil
 | |
| }
 | |
| 
 | |
| func (alwaysDenyUI) ShowError(message string) {
 | |
| 	panic("implement me")
 | |
| }
 | |
| 
 | |
| func (alwaysDenyUI) ShowInfo(message string) {
 | |
| 	panic("implement me")
 | |
| }
 | |
| 
 | |
| func (alwaysDenyUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
 | |
| 	panic("implement me")
 | |
| }
 | |
| 
 | |
| func initRuleEngine(js string) (*rulesetUI, error) {
 | |
| 	r, err := NewRuleEvaluator(&alwaysDenyUI{}, storage.NewEphemeralStorage())
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to create js engine: %v", err)
 | |
| 	}
 | |
| 	if err = r.Init(js); err != nil {
 | |
| 		return nil, fmt.Errorf("failed to load bootstrap js: %v", err)
 | |
| 	}
 | |
| 	return r, nil
 | |
| }
 | |
| 
 | |
| func TestListRequest(t *testing.T) {
 | |
| 	accs := make([]accounts.Account, 5)
 | |
| 
 | |
| 	for i := range accs {
 | |
| 		addr := fmt.Sprintf("000000000000000000000000000000000000000%x", i)
 | |
| 		acc := accounts.Account{
 | |
| 			Address: common.BytesToAddress(common.Hex2Bytes(addr)),
 | |
| 			URL:     accounts.URL{Scheme: "test", Path: fmt.Sprintf("acc-%d", i)},
 | |
| 		}
 | |
| 		accs[i] = acc
 | |
| 	}
 | |
| 
 | |
| 	js := `function ApproveListing(){ return "Approve" }`
 | |
| 
 | |
| 	r, err := initRuleEngine(js)
 | |
| 	if err != nil {
 | |
| 		t.Errorf("Couldn't create evaluator %v", err)
 | |
| 		return
 | |
| 	}
 | |
| 	resp, _ := r.ApproveListing(&core.ListRequest{
 | |
| 		Accounts: accs,
 | |
| 		Meta:     core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"},
 | |
| 	})
 | |
| 	if len(resp.Accounts) != len(accs) {
 | |
| 		t.Errorf("Expected check to resolve to 'Approve'")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestSignTxRequest(t *testing.T) {
 | |
| 
 | |
| 	js := `
 | |
| 	function ApproveTx(r){
 | |
| 		console.log("transaction.from", r.transaction.from);
 | |
| 		console.log("transaction.to", r.transaction.to);
 | |
| 		console.log("transaction.value", r.transaction.value);
 | |
| 		console.log("transaction.nonce", r.transaction.nonce);
 | |
| 		if(r.transaction.from.toLowerCase()=="0x0000000000000000000000000000000000001337"){ return "Approve"}
 | |
| 		if(r.transaction.from.toLowerCase()=="0x000000000000000000000000000000000000dead"){ return "Reject"}
 | |
| 	}`
 | |
| 
 | |
| 	r, err := initRuleEngine(js)
 | |
| 	if err != nil {
 | |
| 		t.Errorf("Couldn't create evaluator %v", err)
 | |
| 		return
 | |
| 	}
 | |
| 	to, err := mixAddr("000000000000000000000000000000000000dead")
 | |
| 	if err != nil {
 | |
| 		t.Error(err)
 | |
| 		return
 | |
| 	}
 | |
| 	from, err := mixAddr("0000000000000000000000000000000000001337")
 | |
| 
 | |
| 	if err != nil {
 | |
| 		t.Error(err)
 | |
| 		return
 | |
| 	}
 | |
| 	t.Logf("to %v", to.Address().String())
 | |
| 	resp, err := r.ApproveTx(&core.SignTxRequest{
 | |
| 		Transaction: core.SendTxArgs{
 | |
| 			From: *from,
 | |
| 			To:   to},
 | |
| 		Callinfo: nil,
 | |
| 		Meta:     core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"},
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		t.Errorf("Unexpected error %v", err)
 | |
| 	}
 | |
| 	if !resp.Approved {
 | |
| 		t.Errorf("Expected check to resolve to 'Approve'")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type dummyUI struct {
 | |
| 	calls []string
 | |
| }
 | |
| 
 | |
| func (d *dummyUI) RegisterUIServer(api *core.UIServerAPI) {
 | |
| 	panic("implement me")
 | |
| }
 | |
| 
 | |
| func (d *dummyUI) OnInputRequired(info core.UserInputRequest) (core.UserInputResponse, error) {
 | |
| 	d.calls = append(d.calls, "OnInputRequired")
 | |
| 	return core.UserInputResponse{}, nil
 | |
| }
 | |
| 
 | |
| func (d *dummyUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
 | |
| 	d.calls = append(d.calls, "ApproveTx")
 | |
| 	return core.SignTxResponse{}, core.ErrRequestDenied
 | |
| }
 | |
| 
 | |
| func (d *dummyUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
 | |
| 	d.calls = append(d.calls, "ApproveSignData")
 | |
| 	return core.SignDataResponse{}, core.ErrRequestDenied
 | |
| }
 | |
| 
 | |
| func (d *dummyUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
 | |
| 	d.calls = append(d.calls, "ApproveListing")
 | |
| 	return core.ListResponse{}, core.ErrRequestDenied
 | |
| }
 | |
| 
 | |
| func (d *dummyUI) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
 | |
| 	d.calls = append(d.calls, "ApproveNewAccount")
 | |
| 	return core.NewAccountResponse{}, core.ErrRequestDenied
 | |
| }
 | |
| 
 | |
| func (d *dummyUI) ShowError(message string) {
 | |
| 	d.calls = append(d.calls, "ShowError")
 | |
| }
 | |
| 
 | |
| func (d *dummyUI) ShowInfo(message string) {
 | |
| 	d.calls = append(d.calls, "ShowInfo")
 | |
| }
 | |
| 
 | |
| func (d *dummyUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
 | |
| 	d.calls = append(d.calls, "OnApprovedTx")
 | |
| }
 | |
| 
 | |
| func (d *dummyUI) OnSignerStartup(info core.StartupInfo) {
 | |
| }
 | |
| 
 | |
| //TestForwarding tests that the rule-engine correctly dispatches requests to the next caller
 | |
| func TestForwarding(t *testing.T) {
 | |
| 
 | |
| 	js := ""
 | |
| 	ui := &dummyUI{make([]string, 0)}
 | |
| 	jsBackend := storage.NewEphemeralStorage()
 | |
| 	r, err := NewRuleEvaluator(ui, jsBackend)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Failed to create js engine: %v", err)
 | |
| 	}
 | |
| 	if err = r.Init(js); err != nil {
 | |
| 		t.Fatalf("Failed to load bootstrap js: %v", err)
 | |
| 	}
 | |
| 	r.ApproveSignData(nil)
 | |
| 	r.ApproveTx(nil)
 | |
| 	r.ApproveNewAccount(nil)
 | |
| 	r.ApproveListing(nil)
 | |
| 	r.ShowError("test")
 | |
| 	r.ShowInfo("test")
 | |
| 
 | |
| 	//This one is not forwarded
 | |
| 	r.OnApprovedTx(ethapi.SignTransactionResult{})
 | |
| 
 | |
| 	expCalls := 6
 | |
| 	if len(ui.calls) != expCalls {
 | |
| 
 | |
| 		t.Errorf("Expected %d forwarded calls, got %d: %s", expCalls, len(ui.calls), strings.Join(ui.calls, ","))
 | |
| 
 | |
| 	}
 | |
| 
 | |
| }
 | |
| 
 | |
| func TestMissingFunc(t *testing.T) {
 | |
| 	r, err := initRuleEngine(JS)
 | |
| 	if err != nil {
 | |
| 		t.Errorf("Couldn't create evaluator %v", err)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	_, err = r.execute("MissingMethod", "test")
 | |
| 
 | |
| 	if err == nil {
 | |
| 		t.Error("Expected error")
 | |
| 	}
 | |
| 
 | |
| 	approved, err := r.checkApproval("MissingMethod", nil, nil)
 | |
| 	if err == nil {
 | |
| 		t.Errorf("Expected missing method to yield error'")
 | |
| 	}
 | |
| 	if approved {
 | |
| 		t.Errorf("Expected missing method to cause non-approval")
 | |
| 	}
 | |
| 	t.Logf("Err %v", err)
 | |
| 
 | |
| }
 | |
| func TestStorage(t *testing.T) {
 | |
| 
 | |
| 	js := `
 | |
| 	function testStorage(){
 | |
| 		storage.put("mykey", "myvalue")
 | |
| 		a = storage.get("mykey")
 | |
| 
 | |
| 		storage.put("mykey", ["a", "list"])  	// Should result in "a,list"
 | |
| 		a += storage.get("mykey")
 | |
| 
 | |
| 
 | |
| 		storage.put("mykey", {"an": "object"}) 	// Should result in "[object Object]"
 | |
| 		a += storage.get("mykey")
 | |
| 
 | |
| 
 | |
| 		storage.put("mykey", JSON.stringify({"an": "object"})) // Should result in '{"an":"object"}'
 | |
| 		a += storage.get("mykey")
 | |
| 
 | |
| 		a += storage.get("missingkey")		//Missing keys should result in empty string
 | |
| 		storage.put("","missing key==noop") // Can't store with 0-length key
 | |
| 		a += storage.get("")				// Should result in ''
 | |
| 
 | |
| 		var b = new BigNumber(2)
 | |
| 		var c = new BigNumber(16)//"0xf0",16)
 | |
| 		var d = b.plus(c)
 | |
| 		console.log(d)
 | |
| 		return a
 | |
| 	}
 | |
| `
 | |
| 	r, err := initRuleEngine(js)
 | |
| 	if err != nil {
 | |
| 		t.Errorf("Couldn't create evaluator %v", err)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	v, err := r.execute("testStorage", nil)
 | |
| 
 | |
| 	if err != nil {
 | |
| 		t.Errorf("Unexpected error %v", err)
 | |
| 	}
 | |
| 	retval := v.ToString().String()
 | |
| 
 | |
| 	if err != nil {
 | |
| 		t.Errorf("Unexpected error %v", err)
 | |
| 	}
 | |
| 	exp := `myvaluea,list[object Object]{"an":"object"}`
 | |
| 	if retval != exp {
 | |
| 		t.Errorf("Unexpected data, expected '%v', got '%v'", exp, retval)
 | |
| 	}
 | |
| 	t.Logf("Err %v", err)
 | |
| 
 | |
| }
 | |
| 
 | |
| const ExampleTxWindow = `
 | |
| 	function big(str){
 | |
| 		if(str.slice(0,2) == "0x"){ return new BigNumber(str.slice(2),16)}
 | |
| 		return new BigNumber(str)
 | |
| 	}
 | |
| 
 | |
| 	// Time window: 1 week
 | |
| 	var window = 1000* 3600*24*7;
 | |
| 
 | |
| 	// Limit : 1 ether
 | |
| 	var limit = new BigNumber("1e18");
 | |
| 
 | |
| 	function isLimitOk(transaction){
 | |
| 		var value = big(transaction.value)
 | |
| 		// Start of our window function
 | |
| 		var windowstart = new Date().getTime() - window;
 | |
| 
 | |
| 		var txs = [];
 | |
| 		var stored = storage.get('txs');
 | |
| 
 | |
| 		if(stored != ""){
 | |
| 			txs = JSON.parse(stored)
 | |
| 		}
 | |
| 		// First, remove all that have passed out of the time-window
 | |
| 		var newtxs = txs.filter(function(tx){return tx.tstamp > windowstart});
 | |
| 		console.log(txs, newtxs.length);
 | |
| 
 | |
| 		// Secondly, aggregate the current sum
 | |
| 		sum = new BigNumber(0)
 | |
| 
 | |
| 		sum = newtxs.reduce(function(agg, tx){ return big(tx.value).plus(agg)}, sum);
 | |
| 		console.log("ApproveTx > Sum so far", sum);
 | |
| 		console.log("ApproveTx > Requested", value.toNumber());
 | |
| 
 | |
| 		// Would we exceed weekly limit ?
 | |
| 		return sum.plus(value).lt(limit)
 | |
| 
 | |
| 	}
 | |
| 	function ApproveTx(r){
 | |
| 		console.log(r)
 | |
| 		console.log(typeof(r))
 | |
| 		if (isLimitOk(r.transaction)){
 | |
| 			return "Approve"
 | |
| 		}
 | |
| 		return "Nope"
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	* OnApprovedTx(str) is called when a transaction has been approved and signed. The parameter
 | |
|  	* 'response_str' contains the return value that will be sent to the external caller.
 | |
| 	* The return value from this method is ignore - the reason for having this callback is to allow the
 | |
| 	* ruleset to keep track of approved transactions.
 | |
| 	*
 | |
| 	* When implementing rate-limited rules, this callback should be used.
 | |
| 	* If a rule responds with neither 'Approve' nor 'Reject' - the tx goes to manual processing. If the user
 | |
| 	* then accepts the transaction, this method will be called.
 | |
| 	*
 | |
| 	* TLDR; Use this method to keep track of signed transactions, instead of using the data in ApproveTx.
 | |
| 	*/
 | |
|  	function OnApprovedTx(resp){
 | |
| 		var value = big(resp.tx.value)
 | |
| 		var txs = []
 | |
| 		// Load stored transactions
 | |
| 		var stored = storage.get('txs');
 | |
| 		if(stored != ""){
 | |
| 			txs = JSON.parse(stored)
 | |
| 		}
 | |
| 		// Add this to the storage
 | |
| 		txs.push({tstamp: new Date().getTime(), value: value});
 | |
| 		storage.put("txs", JSON.stringify(txs));
 | |
| 	}
 | |
| 
 | |
| `
 | |
| 
 | |
| func dummyTx(value hexutil.Big) *core.SignTxRequest {
 | |
| 	to, _ := mixAddr("000000000000000000000000000000000000dead")
 | |
| 	from, _ := mixAddr("000000000000000000000000000000000000dead")
 | |
| 	n := hexutil.Uint64(3)
 | |
| 	gas := hexutil.Uint64(21000)
 | |
| 	gasPrice := hexutil.Big(*big.NewInt(2000000))
 | |
| 
 | |
| 	return &core.SignTxRequest{
 | |
| 		Transaction: core.SendTxArgs{
 | |
| 			From:     *from,
 | |
| 			To:       to,
 | |
| 			Value:    value,
 | |
| 			Nonce:    n,
 | |
| 			GasPrice: gasPrice,
 | |
| 			Gas:      gas,
 | |
| 		},
 | |
| 		Callinfo: []core.ValidationInfo{
 | |
| 			{Typ: "Warning", Message: "All your base are bellong to us"},
 | |
| 		},
 | |
| 		Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func dummyTxWithV(value uint64) *core.SignTxRequest {
 | |
| 	v := big.NewInt(0).SetUint64(value)
 | |
| 	h := hexutil.Big(*v)
 | |
| 	return dummyTx(h)
 | |
| }
 | |
| 
 | |
| func dummySigned(value *big.Int) *types.Transaction {
 | |
| 	to := common.HexToAddress("000000000000000000000000000000000000dead")
 | |
| 	gas := uint64(21000)
 | |
| 	gasPrice := big.NewInt(2000000)
 | |
| 	data := make([]byte, 0)
 | |
| 	return types.NewTransaction(3, to, value, gas, gasPrice, data)
 | |
| }
 | |
| 
 | |
| func TestLimitWindow(t *testing.T) {
 | |
| 	r, err := initRuleEngine(ExampleTxWindow)
 | |
| 	if err != nil {
 | |
| 		t.Errorf("Couldn't create evaluator %v", err)
 | |
| 		return
 | |
| 	}
 | |
| 	// 0.3 ether: 429D069189E0000 wei
 | |
| 	v := big.NewInt(0).SetBytes(common.Hex2Bytes("0429D069189E0000"))
 | |
| 	h := hexutil.Big(*v)
 | |
| 	// The first three should succeed
 | |
| 	for i := 0; i < 3; i++ {
 | |
| 		unsigned := dummyTx(h)
 | |
| 		resp, err := r.ApproveTx(unsigned)
 | |
| 		if err != nil {
 | |
| 			t.Errorf("Unexpected error %v", err)
 | |
| 		}
 | |
| 		if !resp.Approved {
 | |
| 			t.Errorf("Expected check to resolve to 'Approve'")
 | |
| 		}
 | |
| 		// Create a dummy signed transaction
 | |
| 
 | |
| 		response := ethapi.SignTransactionResult{
 | |
| 			Tx:  dummySigned(v),
 | |
| 			Raw: common.Hex2Bytes("deadbeef"),
 | |
| 		}
 | |
| 		r.OnApprovedTx(response)
 | |
| 	}
 | |
| 	// Fourth should fail
 | |
| 	resp, _ := r.ApproveTx(dummyTx(h))
 | |
| 	if resp.Approved {
 | |
| 		t.Errorf("Expected check to resolve to 'Reject'")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // dontCallMe is used as a next-handler that does not want to be called - it invokes test failure
 | |
| type dontCallMe struct {
 | |
| 	t *testing.T
 | |
| }
 | |
| 
 | |
| func (d *dontCallMe) OnInputRequired(info core.UserInputRequest) (core.UserInputResponse, error) {
 | |
| 	d.t.Fatalf("Did not expect next-handler to be called")
 | |
| 	return core.UserInputResponse{}, nil
 | |
| }
 | |
| 
 | |
| func (d *dontCallMe) RegisterUIServer(api *core.UIServerAPI) {
 | |
| }
 | |
| 
 | |
| func (d *dontCallMe) OnSignerStartup(info core.StartupInfo) {
 | |
| }
 | |
| 
 | |
| func (d *dontCallMe) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
 | |
| 	d.t.Fatalf("Did not expect next-handler to be called")
 | |
| 	return core.SignTxResponse{}, core.ErrRequestDenied
 | |
| }
 | |
| 
 | |
| func (d *dontCallMe) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
 | |
| 	d.t.Fatalf("Did not expect next-handler to be called")
 | |
| 	return core.SignDataResponse{}, core.ErrRequestDenied
 | |
| }
 | |
| 
 | |
| func (d *dontCallMe) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
 | |
| 	d.t.Fatalf("Did not expect next-handler to be called")
 | |
| 	return core.ListResponse{}, core.ErrRequestDenied
 | |
| }
 | |
| 
 | |
| func (d *dontCallMe) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
 | |
| 	d.t.Fatalf("Did not expect next-handler to be called")
 | |
| 	return core.NewAccountResponse{}, core.ErrRequestDenied
 | |
| }
 | |
| 
 | |
| func (d *dontCallMe) ShowError(message string) {
 | |
| 	d.t.Fatalf("Did not expect next-handler to be called")
 | |
| }
 | |
| 
 | |
| func (d *dontCallMe) ShowInfo(message string) {
 | |
| 	d.t.Fatalf("Did not expect next-handler to be called")
 | |
| }
 | |
| 
 | |
| func (d *dontCallMe) OnApprovedTx(tx ethapi.SignTransactionResult) {
 | |
| 	d.t.Fatalf("Did not expect next-handler to be called")
 | |
| }
 | |
| 
 | |
| //TestContextIsCleared tests that the rule-engine does not retain variables over several requests.
 | |
| // if it does, that would be bad since developers may rely on that to store data,
 | |
| // instead of using the disk-based data storage
 | |
| func TestContextIsCleared(t *testing.T) {
 | |
| 
 | |
| 	js := `
 | |
| 	function ApproveTx(){
 | |
| 		if (typeof foobar == 'undefined') {
 | |
| 			foobar = "Approve"
 | |
|  		}
 | |
| 		console.log(foobar)
 | |
| 		if (foobar == "Approve"){
 | |
| 			foobar = "Reject"
 | |
| 		}else{
 | |
| 			foobar = "Approve"
 | |
| 		}
 | |
| 		return foobar
 | |
| 	}
 | |
| 	`
 | |
| 	ui := &dontCallMe{t}
 | |
| 	r, err := NewRuleEvaluator(ui, storage.NewEphemeralStorage())
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Failed to create js engine: %v", err)
 | |
| 	}
 | |
| 	if err = r.Init(js); err != nil {
 | |
| 		t.Fatalf("Failed to load bootstrap js: %v", err)
 | |
| 	}
 | |
| 	tx := dummyTxWithV(0)
 | |
| 	r1, _ := r.ApproveTx(tx)
 | |
| 	r2, _ := r.ApproveTx(tx)
 | |
| 	if r1.Approved != r2.Approved {
 | |
| 		t.Errorf("Expected execution context to be cleared between executions")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestSignData(t *testing.T) {
 | |
| 
 | |
| 	js := `function ApproveListing(){
 | |
|     return "Approve"
 | |
| }
 | |
| function ApproveSignData(r){
 | |
|     if( r.address.toLowerCase() == "0x694267f14675d7e1b9494fd8d72fefe1755710fa")
 | |
|     {
 | |
|         if(r.messages[0].value.indexOf("bazonk") >= 0){
 | |
|             return "Approve"
 | |
|         }
 | |
|         return "Reject"
 | |
|     }
 | |
|     // Otherwise goes to manual processing
 | |
| }`
 | |
| 	r, err := initRuleEngine(js)
 | |
| 	if err != nil {
 | |
| 		t.Errorf("Couldn't create evaluator %v", err)
 | |
| 		return
 | |
| 	}
 | |
| 	message := "baz bazonk foo"
 | |
| 	hash, rawdata := accounts.TextAndHash([]byte(message))
 | |
| 	addr, _ := mixAddr("0x694267f14675d7e1b9494fd8d72fefe1755710fa")
 | |
| 
 | |
| 	t.Logf("address %v %v\n", addr.String(), addr.Original())
 | |
| 
 | |
| 	nvt := []*core.NameValueType{
 | |
| 		{
 | |
| 			Name:  "message",
 | |
| 			Typ:   "text/plain",
 | |
| 			Value: message,
 | |
| 		},
 | |
| 	}
 | |
| 	resp, err := r.ApproveSignData(&core.SignDataRequest{
 | |
| 		Address:  *addr,
 | |
| 		Messages: nvt,
 | |
| 		Hash:     hash,
 | |
| 		Meta:     core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"},
 | |
| 		Rawdata:  []byte(rawdata),
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Unexpected error %v", err)
 | |
| 	}
 | |
| 	if !resp.Approved {
 | |
| 		t.Fatalf("Expected approved")
 | |
| 	}
 | |
| }
 |