rules_test.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. // Copyright 2018 The go-ethereum Authors
  2. // This file is part of go-ethereum.
  3. //
  4. // go-ethereum is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // go-ethereum is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
  16. //
  17. package rules
  18. import (
  19. "fmt"
  20. "math/big"
  21. "strings"
  22. "testing"
  23. "github.com/ethereum/go-ethereum/accounts"
  24. "github.com/ethereum/go-ethereum/common"
  25. "github.com/ethereum/go-ethereum/common/hexutil"
  26. "github.com/ethereum/go-ethereum/core/types"
  27. "github.com/ethereum/go-ethereum/internal/ethapi"
  28. "github.com/ethereum/go-ethereum/signer/core"
  29. "github.com/ethereum/go-ethereum/signer/storage"
  30. )
  31. const JS = `
  32. /**
  33. This is an example implementation of a Javascript rule file.
  34. When the signer receives a request over the external API, the corresponding method is evaluated.
  35. Three things can happen:
  36. 1. The method returns "Approve". This means the operation is permitted.
  37. 2. The method returns "Reject". This means the operation is rejected.
  38. 3. Anything else; other return values [*], method not implemented or exception occurred during processing. This means
  39. that the operation will continue to manual processing, via the regular UI method chosen by the user.
  40. [*] Note: Future version of the ruleset may use more complex json-based returnvalues, making it possible to not
  41. only respond Approve/Reject/Manual, but also modify responses. For example, choose to list only one, but not all
  42. accounts in a list-request. The points above will continue to hold for non-json based responses ("Approve"/"Reject").
  43. **/
  44. function ApproveListing(request){
  45. console.log("In js approve listing");
  46. console.log(request.accounts[3].Address)
  47. console.log(request.meta.Remote)
  48. return "Approve"
  49. }
  50. function ApproveTx(request){
  51. console.log("test");
  52. console.log("from");
  53. return "Reject";
  54. }
  55. function test(thing){
  56. console.log(thing.String())
  57. }
  58. `
  59. func mixAddr(a string) (*common.MixedcaseAddress, error) {
  60. return common.NewMixedcaseAddressFromString(a)
  61. }
  62. type alwaysDenyUI struct{}
  63. func (alwaysDenyUI) OnInputRequired(info core.UserInputRequest) (core.UserInputResponse, error) {
  64. return core.UserInputResponse{}, nil
  65. }
  66. func (alwaysDenyUI) OnSignerStartup(info core.StartupInfo) {
  67. }
  68. func (alwaysDenyUI) OnMasterPassword(request *core.PasswordRequest) (core.PasswordResponse, error) {
  69. return core.PasswordResponse{}, nil
  70. }
  71. func (alwaysDenyUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
  72. return core.SignTxResponse{Transaction: request.Transaction, Approved: false, Password: ""}, nil
  73. }
  74. func (alwaysDenyUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
  75. return core.SignDataResponse{Approved: false, Password: ""}, nil
  76. }
  77. func (alwaysDenyUI) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) {
  78. return core.ExportResponse{Approved: false}, nil
  79. }
  80. func (alwaysDenyUI) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) {
  81. return core.ImportResponse{Approved: false, OldPassword: "", NewPassword: ""}, nil
  82. }
  83. func (alwaysDenyUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
  84. return core.ListResponse{Accounts: nil}, nil
  85. }
  86. func (alwaysDenyUI) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
  87. return core.NewAccountResponse{Approved: false, Password: ""}, nil
  88. }
  89. func (alwaysDenyUI) ShowError(message string) {
  90. panic("implement me")
  91. }
  92. func (alwaysDenyUI) ShowInfo(message string) {
  93. panic("implement me")
  94. }
  95. func (alwaysDenyUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
  96. panic("implement me")
  97. }
  98. func initRuleEngine(js string) (*rulesetUI, error) {
  99. r, err := NewRuleEvaluator(&alwaysDenyUI{}, storage.NewEphemeralStorage(), storage.NewEphemeralStorage())
  100. if err != nil {
  101. return nil, fmt.Errorf("failed to create js engine: %v", err)
  102. }
  103. if err = r.Init(js); err != nil {
  104. return nil, fmt.Errorf("failed to load bootstrap js: %v", err)
  105. }
  106. return r, nil
  107. }
  108. func TestListRequest(t *testing.T) {
  109. accs := make([]core.Account, 5)
  110. for i := range accs {
  111. addr := fmt.Sprintf("000000000000000000000000000000000000000%x", i)
  112. acc := core.Account{
  113. Address: common.BytesToAddress(common.Hex2Bytes(addr)),
  114. URL: accounts.URL{Scheme: "test", Path: fmt.Sprintf("acc-%d", i)},
  115. }
  116. accs[i] = acc
  117. }
  118. js := `function ApproveListing(){ return "Approve" }`
  119. r, err := initRuleEngine(js)
  120. if err != nil {
  121. t.Errorf("Couldn't create evaluator %v", err)
  122. return
  123. }
  124. resp, _ := r.ApproveListing(&core.ListRequest{
  125. Accounts: accs,
  126. Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"},
  127. })
  128. if len(resp.Accounts) != len(accs) {
  129. t.Errorf("Expected check to resolve to 'Approve'")
  130. }
  131. }
  132. func TestSignTxRequest(t *testing.T) {
  133. js := `
  134. function ApproveTx(r){
  135. console.log("transaction.from", r.transaction.from);
  136. console.log("transaction.to", r.transaction.to);
  137. console.log("transaction.value", r.transaction.value);
  138. console.log("transaction.nonce", r.transaction.nonce);
  139. if(r.transaction.from.toLowerCase()=="0x0000000000000000000000000000000000001337"){ return "Approve"}
  140. if(r.transaction.from.toLowerCase()=="0x000000000000000000000000000000000000dead"){ return "Reject"}
  141. }`
  142. r, err := initRuleEngine(js)
  143. if err != nil {
  144. t.Errorf("Couldn't create evaluator %v", err)
  145. return
  146. }
  147. to, err := mixAddr("000000000000000000000000000000000000dead")
  148. if err != nil {
  149. t.Error(err)
  150. return
  151. }
  152. from, err := mixAddr("0000000000000000000000000000000000001337")
  153. if err != nil {
  154. t.Error(err)
  155. return
  156. }
  157. fmt.Printf("to %v", to.Address().String())
  158. resp, err := r.ApproveTx(&core.SignTxRequest{
  159. Transaction: core.SendTxArgs{
  160. From: *from,
  161. To: to},
  162. Callinfo: nil,
  163. Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"},
  164. })
  165. if err != nil {
  166. t.Errorf("Unexpected error %v", err)
  167. }
  168. if !resp.Approved {
  169. t.Errorf("Expected check to resolve to 'Approve'")
  170. }
  171. }
  172. type dummyUI struct {
  173. calls []string
  174. }
  175. func (d *dummyUI) OnInputRequired(info core.UserInputRequest) (core.UserInputResponse, error) {
  176. d.calls = append(d.calls, "OnInputRequired")
  177. return core.UserInputResponse{}, nil
  178. }
  179. func (d *dummyUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
  180. d.calls = append(d.calls, "ApproveTx")
  181. return core.SignTxResponse{}, core.ErrRequestDenied
  182. }
  183. func (d *dummyUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
  184. d.calls = append(d.calls, "ApproveSignData")
  185. return core.SignDataResponse{}, core.ErrRequestDenied
  186. }
  187. func (d *dummyUI) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) {
  188. d.calls = append(d.calls, "ApproveExport")
  189. return core.ExportResponse{}, core.ErrRequestDenied
  190. }
  191. func (d *dummyUI) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) {
  192. d.calls = append(d.calls, "ApproveImport")
  193. return core.ImportResponse{}, core.ErrRequestDenied
  194. }
  195. func (d *dummyUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
  196. d.calls = append(d.calls, "ApproveListing")
  197. return core.ListResponse{}, core.ErrRequestDenied
  198. }
  199. func (d *dummyUI) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
  200. d.calls = append(d.calls, "ApproveNewAccount")
  201. return core.NewAccountResponse{}, core.ErrRequestDenied
  202. }
  203. func (d *dummyUI) ShowError(message string) {
  204. d.calls = append(d.calls, "ShowError")
  205. }
  206. func (d *dummyUI) ShowInfo(message string) {
  207. d.calls = append(d.calls, "ShowInfo")
  208. }
  209. func (d *dummyUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
  210. d.calls = append(d.calls, "OnApprovedTx")
  211. }
  212. func (d *dummyUI) OnMasterPassword(request *core.PasswordRequest) (core.PasswordResponse, error) {
  213. return core.PasswordResponse{}, nil
  214. }
  215. func (d *dummyUI) OnSignerStartup(info core.StartupInfo) {
  216. }
  217. //TestForwarding tests that the rule-engine correctly dispatches requests to the next caller
  218. func TestForwarding(t *testing.T) {
  219. js := ""
  220. ui := &dummyUI{make([]string, 0)}
  221. jsBackend := storage.NewEphemeralStorage()
  222. credBackend := storage.NewEphemeralStorage()
  223. r, err := NewRuleEvaluator(ui, jsBackend, credBackend)
  224. if err != nil {
  225. t.Fatalf("Failed to create js engine: %v", err)
  226. }
  227. if err = r.Init(js); err != nil {
  228. t.Fatalf("Failed to load bootstrap js: %v", err)
  229. }
  230. r.ApproveSignData(nil)
  231. r.ApproveTx(nil)
  232. r.ApproveImport(nil)
  233. r.ApproveNewAccount(nil)
  234. r.ApproveListing(nil)
  235. r.ApproveExport(nil)
  236. r.ShowError("test")
  237. r.ShowInfo("test")
  238. //This one is not forwarded
  239. r.OnApprovedTx(ethapi.SignTransactionResult{})
  240. expCalls := 8
  241. if len(ui.calls) != expCalls {
  242. t.Errorf("Expected %d forwarded calls, got %d: %s", expCalls, len(ui.calls), strings.Join(ui.calls, ","))
  243. }
  244. }
  245. func TestMissingFunc(t *testing.T) {
  246. r, err := initRuleEngine(JS)
  247. if err != nil {
  248. t.Errorf("Couldn't create evaluator %v", err)
  249. return
  250. }
  251. _, err = r.execute("MissingMethod", "test")
  252. if err == nil {
  253. t.Error("Expected error")
  254. }
  255. approved, err := r.checkApproval("MissingMethod", nil, nil)
  256. if err == nil {
  257. t.Errorf("Expected missing method to yield error'")
  258. }
  259. if approved {
  260. t.Errorf("Expected missing method to cause non-approval")
  261. }
  262. fmt.Printf("Err %v", err)
  263. }
  264. func TestStorage(t *testing.T) {
  265. js := `
  266. function testStorage(){
  267. storage.Put("mykey", "myvalue")
  268. a = storage.Get("mykey")
  269. storage.Put("mykey", ["a", "list"]) // Should result in "a,list"
  270. a += storage.Get("mykey")
  271. storage.Put("mykey", {"an": "object"}) // Should result in "[object Object]"
  272. a += storage.Get("mykey")
  273. storage.Put("mykey", JSON.stringify({"an": "object"})) // Should result in '{"an":"object"}'
  274. a += storage.Get("mykey")
  275. a += storage.Get("missingkey") //Missing keys should result in empty string
  276. storage.Put("","missing key==noop") // Can't store with 0-length key
  277. a += storage.Get("") // Should result in ''
  278. var b = new BigNumber(2)
  279. var c = new BigNumber(16)//"0xf0",16)
  280. var d = b.plus(c)
  281. console.log(d)
  282. return a
  283. }
  284. `
  285. r, err := initRuleEngine(js)
  286. if err != nil {
  287. t.Errorf("Couldn't create evaluator %v", err)
  288. return
  289. }
  290. v, err := r.execute("testStorage", nil)
  291. if err != nil {
  292. t.Errorf("Unexpected error %v", err)
  293. }
  294. retval, err := v.ToString()
  295. if err != nil {
  296. t.Errorf("Unexpected error %v", err)
  297. }
  298. exp := `myvaluea,list[object Object]{"an":"object"}`
  299. if retval != exp {
  300. t.Errorf("Unexpected data, expected '%v', got '%v'", exp, retval)
  301. }
  302. fmt.Printf("Err %v", err)
  303. }
  304. const ExampleTxWindow = `
  305. function big(str){
  306. if(str.slice(0,2) == "0x"){ return new BigNumber(str.slice(2),16)}
  307. return new BigNumber(str)
  308. }
  309. // Time window: 1 week
  310. var window = 1000* 3600*24*7;
  311. // Limit : 1 ether
  312. var limit = new BigNumber("1e18");
  313. function isLimitOk(transaction){
  314. var value = big(transaction.value)
  315. // Start of our window function
  316. var windowstart = new Date().getTime() - window;
  317. var txs = [];
  318. var stored = storage.Get('txs');
  319. if(stored != ""){
  320. txs = JSON.parse(stored)
  321. }
  322. // First, remove all that have passed out of the time-window
  323. var newtxs = txs.filter(function(tx){return tx.tstamp > windowstart});
  324. console.log(txs, newtxs.length);
  325. // Secondly, aggregate the current sum
  326. sum = new BigNumber(0)
  327. sum = newtxs.reduce(function(agg, tx){ return big(tx.value).plus(agg)}, sum);
  328. console.log("ApproveTx > Sum so far", sum);
  329. console.log("ApproveTx > Requested", value.toNumber());
  330. // Would we exceed weekly limit ?
  331. return sum.plus(value).lt(limit)
  332. }
  333. function ApproveTx(r){
  334. console.log(r)
  335. console.log(typeof(r))
  336. if (isLimitOk(r.transaction)){
  337. return "Approve"
  338. }
  339. return "Nope"
  340. }
  341. /**
  342. * OnApprovedTx(str) is called when a transaction has been approved and signed. The parameter
  343. * 'response_str' contains the return value that will be sent to the external caller.
  344. * The return value from this method is ignore - the reason for having this callback is to allow the
  345. * ruleset to keep track of approved transactions.
  346. *
  347. * When implementing rate-limited rules, this callback should be used.
  348. * If a rule responds with neither 'Approve' nor 'Reject' - the tx goes to manual processing. If the user
  349. * then accepts the transaction, this method will be called.
  350. *
  351. * TLDR; Use this method to keep track of signed transactions, instead of using the data in ApproveTx.
  352. */
  353. function OnApprovedTx(resp){
  354. var value = big(resp.tx.value)
  355. var txs = []
  356. // Load stored transactions
  357. var stored = storage.Get('txs');
  358. if(stored != ""){
  359. txs = JSON.parse(stored)
  360. }
  361. // Add this to the storage
  362. txs.push({tstamp: new Date().getTime(), value: value});
  363. storage.Put("txs", JSON.stringify(txs));
  364. }
  365. `
  366. func dummyTx(value hexutil.Big) *core.SignTxRequest {
  367. to, _ := mixAddr("000000000000000000000000000000000000dead")
  368. from, _ := mixAddr("000000000000000000000000000000000000dead")
  369. n := hexutil.Uint64(3)
  370. gas := hexutil.Uint64(21000)
  371. gasPrice := hexutil.Big(*big.NewInt(2000000))
  372. return &core.SignTxRequest{
  373. Transaction: core.SendTxArgs{
  374. From: *from,
  375. To: to,
  376. Value: value,
  377. Nonce: n,
  378. GasPrice: gasPrice,
  379. Gas: gas,
  380. },
  381. Callinfo: []core.ValidationInfo{
  382. {Typ: "Warning", Message: "All your base are bellong to us"},
  383. },
  384. Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"},
  385. }
  386. }
  387. func dummyTxWithV(value uint64) *core.SignTxRequest {
  388. v := big.NewInt(0).SetUint64(value)
  389. h := hexutil.Big(*v)
  390. return dummyTx(h)
  391. }
  392. func dummySigned(value *big.Int) *types.Transaction {
  393. to := common.HexToAddress("000000000000000000000000000000000000dead")
  394. gas := uint64(21000)
  395. gasPrice := big.NewInt(2000000)
  396. data := make([]byte, 0)
  397. return types.NewTransaction(3, to, value, gas, gasPrice, data)
  398. }
  399. func TestLimitWindow(t *testing.T) {
  400. r, err := initRuleEngine(ExampleTxWindow)
  401. if err != nil {
  402. t.Errorf("Couldn't create evaluator %v", err)
  403. return
  404. }
  405. // 0.3 ether: 429D069189E0000 wei
  406. v := big.NewInt(0).SetBytes(common.Hex2Bytes("0429D069189E0000"))
  407. h := hexutil.Big(*v)
  408. // The first three should succeed
  409. for i := 0; i < 3; i++ {
  410. unsigned := dummyTx(h)
  411. resp, err := r.ApproveTx(unsigned)
  412. if err != nil {
  413. t.Errorf("Unexpected error %v", err)
  414. }
  415. if !resp.Approved {
  416. t.Errorf("Expected check to resolve to 'Approve'")
  417. }
  418. // Create a dummy signed transaction
  419. response := ethapi.SignTransactionResult{
  420. Tx: dummySigned(v),
  421. Raw: common.Hex2Bytes("deadbeef"),
  422. }
  423. r.OnApprovedTx(response)
  424. }
  425. // Fourth should fail
  426. resp, _ := r.ApproveTx(dummyTx(h))
  427. if resp.Approved {
  428. t.Errorf("Expected check to resolve to 'Reject'")
  429. }
  430. }
  431. // dontCallMe is used as a next-handler that does not want to be called - it invokes test failure
  432. type dontCallMe struct {
  433. t *testing.T
  434. }
  435. func (d *dontCallMe) OnInputRequired(info core.UserInputRequest) (core.UserInputResponse, error) {
  436. d.t.Fatalf("Did not expect next-handler to be called")
  437. return core.UserInputResponse{}, nil
  438. }
  439. func (d *dontCallMe) OnSignerStartup(info core.StartupInfo) {
  440. }
  441. func (d *dontCallMe) OnMasterPassword(request *core.PasswordRequest) (core.PasswordResponse, error) {
  442. return core.PasswordResponse{}, nil
  443. }
  444. func (d *dontCallMe) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
  445. d.t.Fatalf("Did not expect next-handler to be called")
  446. return core.SignTxResponse{}, core.ErrRequestDenied
  447. }
  448. func (d *dontCallMe) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
  449. d.t.Fatalf("Did not expect next-handler to be called")
  450. return core.SignDataResponse{}, core.ErrRequestDenied
  451. }
  452. func (d *dontCallMe) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) {
  453. d.t.Fatalf("Did not expect next-handler to be called")
  454. return core.ExportResponse{}, core.ErrRequestDenied
  455. }
  456. func (d *dontCallMe) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) {
  457. d.t.Fatalf("Did not expect next-handler to be called")
  458. return core.ImportResponse{}, core.ErrRequestDenied
  459. }
  460. func (d *dontCallMe) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
  461. d.t.Fatalf("Did not expect next-handler to be called")
  462. return core.ListResponse{}, core.ErrRequestDenied
  463. }
  464. func (d *dontCallMe) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
  465. d.t.Fatalf("Did not expect next-handler to be called")
  466. return core.NewAccountResponse{}, core.ErrRequestDenied
  467. }
  468. func (d *dontCallMe) ShowError(message string) {
  469. d.t.Fatalf("Did not expect next-handler to be called")
  470. }
  471. func (d *dontCallMe) ShowInfo(message string) {
  472. d.t.Fatalf("Did not expect next-handler to be called")
  473. }
  474. func (d *dontCallMe) OnApprovedTx(tx ethapi.SignTransactionResult) {
  475. d.t.Fatalf("Did not expect next-handler to be called")
  476. }
  477. //TestContextIsCleared tests that the rule-engine does not retain variables over several requests.
  478. // if it does, that would be bad since developers may rely on that to store data,
  479. // instead of using the disk-based data storage
  480. func TestContextIsCleared(t *testing.T) {
  481. js := `
  482. function ApproveTx(){
  483. if (typeof foobar == 'undefined') {
  484. foobar = "Approve"
  485. }
  486. console.log(foobar)
  487. if (foobar == "Approve"){
  488. foobar = "Reject"
  489. }else{
  490. foobar = "Approve"
  491. }
  492. return foobar
  493. }
  494. `
  495. ui := &dontCallMe{t}
  496. r, err := NewRuleEvaluator(ui, storage.NewEphemeralStorage(), storage.NewEphemeralStorage())
  497. if err != nil {
  498. t.Fatalf("Failed to create js engine: %v", err)
  499. }
  500. if err = r.Init(js); err != nil {
  501. t.Fatalf("Failed to load bootstrap js: %v", err)
  502. }
  503. tx := dummyTxWithV(0)
  504. r1, _ := r.ApproveTx(tx)
  505. r2, _ := r.ApproveTx(tx)
  506. if r1.Approved != r2.Approved {
  507. t.Errorf("Expected execution context to be cleared between executions")
  508. }
  509. }
  510. func TestSignData(t *testing.T) {
  511. js := `function ApproveListing(){
  512. return "Approve"
  513. }
  514. function ApproveSignData(r){
  515. if( r.address.toLowerCase() == "0x694267f14675d7e1b9494fd8d72fefe1755710fa")
  516. {
  517. if(r.message[0].value.indexOf("bazonk") >= 0){
  518. return "Approve"
  519. }
  520. return "Reject"
  521. }
  522. // Otherwise goes to manual processing
  523. }`
  524. r, err := initRuleEngine(js)
  525. if err != nil {
  526. t.Errorf("Couldn't create evaluator %v", err)
  527. return
  528. }
  529. message := "baz bazonk foo"
  530. hash, rawdata := accounts.TextAndHash([]byte(message))
  531. addr, _ := mixAddr("0x694267f14675d7e1b9494fd8d72fefe1755710fa")
  532. fmt.Printf("address %v %v\n", addr.String(), addr.Original())
  533. nvt := []*core.NameValueType{
  534. {
  535. Name: "message",
  536. Typ: "text/plain",
  537. Value: message,
  538. },
  539. }
  540. resp, err := r.ApproveSignData(&core.SignDataRequest{
  541. Address: *addr,
  542. Message: nvt,
  543. Hash: hash,
  544. Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"},
  545. Rawdata: []byte(rawdata),
  546. })
  547. if err != nil {
  548. t.Fatalf("Unexpected error %v", err)
  549. }
  550. if !resp.Approved {
  551. t.Fatalf("Expected approved")
  552. }
  553. }