web3dart icon indicating copy to clipboard operation
web3dart copied to clipboard

Tried to call a transaction that modifys state

Open mtellect opened this issue 6 years ago • 5 comments

Error Log

[VERBOSE-2:ui_dart_state.cc(148)] Unhandled Exception: Exception: Tried to call a transaction that modifys state
#0      FinalizedTransaction.call (package:web3dart/src/transaction.dart:124:4)
#1      _MyHomePageState._getSmartCoursesContract (package:flutter_token/main.dart:94:32)
<asynchronous suspension>
#2      _MyHomePageState.initState (package:flutter_token/main.dart:170:5)
#3      StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:3846:58)
#4      ComponentElement.mount (package:flutter/src/widgets/framework.dart:3711:5)
#5      Element.inflateWidget (package:flutter/src/widgets/framework.dart:2956:14)
#6      Element.updateChild (package:flutter/src/widgets/framework.dart:2759:12)
#7      SingleChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:4876:14)
#8      Element.inflateWidget (package:flutter/src/widgets/framework.dart:2956:14)
#9      Element.updateChild (package:flutter/src/widgets/framework.dart:2759:12)
#10     ComponentElement.performR<…>

Contract ABI

const List coursesABI = [
  {
    "constant": true,
    "inputs": [
      {"name": "_address", "type": "address"}
    ],
    "name": "getInstructor",
    "outputs": [
      {"name": "", "type": "string"},
      {"name": "", "type": "string"},
      {"name": "", "type": "uint256"}
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [],
    "name": "getInstructors",
    "outputs": [
      {"name": "", "type": "address[]"}
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      {"name": "_address", "type": "address"},
      {"name": "_fname", "type": "string"},
      {"name": "_lname", "type": "string"},
      {"name": "_age", "type": "uint256"}
    ],
    "name": "setInstructor",
    "outputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [],
    "name": "countInstructors",
    "outputs": [
      {"name": "", "type": "uint256"}
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [
      {"name": "", "type": "uint256"}
    ],
    "name": "instructorAccounts",
    "outputs": [
      {"name": "", "type": "address"}
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  }
];

My function

 void _getSmartCoursesContract() async {
    String courseContractABI = json.encode(coursesABI);
    print(courseContractABI);
    String tokenAddress = "0x8b373636Ff3147FCc01B40b57114eC282e77E2df";
    String privateKeyHex =
        "79FD6A46633A4AF7B1C3C1CA1D5EDFEC35245D42A5A3661B29AFFD8263E32853";

    var httpClient = new Client();
    var ethClient = new Web3Client(apiUrl, httpClient);
    var credentials = Credentials.fromPrivateKeyHex(privateKeyHex);

    var contractHelloABI =
        ContractABI.parseFromJSON(courseContractABI, "Courses");
    var coursesContract = new DeployedContract(contractHelloABI,
        new EthereumAddress(tokenAddress), ethClient, credentials);

    var getCoursesFn =
        coursesContract.findFunctionsByName("countInstructors").first;
    var listTokenFn = coursesContract.functions;

    print(listTokenFn[2].name);

//    for (FunctionParameter fp in getCoursesFn.parameters) {
//      print("Function ${getCoursesFn.name} ---- Parameters ${fp.name}");
//    }
//
//    for (int a = 0; a < listTokenFn.length; a++) {
//      print(listTokenFn[a].name);
//    }

//    for (ContractFunction fn in listTokenFn) {
//      for (FunctionParameter fp in fn.parameters) {
//        print("Function ${fn.name} ---- Parameters ${fp.name}");
//      }
//    }

    var age = new BigInt.from(24);

    var coursesResponse = new Transaction(keys: credentials, maximumGas: 0);
    FinalizedTransaction finalizedTransaction = coursesResponse.prepareForCall(
      coursesContract,
      listTokenFn[2],
      [credentials.address.number, "Maugost", "Okore", age],
    );
    await finalizedTransaction.call(ethClient).then((result) {
      //print(result);
    });
  }

mtellect avatar Feb 24 '19 00:02 mtellect

You're trying to call setInstructor on the smart contract, right? Here, "modifies state" means that calling the function will write data onto the Blockchain: The function has a mutability of nonPayable, which means that the contract can still change its internal state. Call-ing a transaction means that it will be executed as read-only. This means that the transaction is not actually sent to the network. Instead, the node you're connected with will execute the called function locally, which is free to use. Of course, this can't work when you're writing data, as that needs to be properly synchronized around the network. What you'll want to use instead is await finalizedTransaction.send(ethClient). Notice that writes won't work with a maximum gas of 0, you'll have to spend some Ether on that operation. Please let me know if that doesn't answer your question, thanks.

simolus3 avatar Feb 24 '19 09:02 simolus3

Thanks for your swift reply

I get this error

Performing hot restart...
Syncing files to device iPhone X...
Restarted application in 5,929ms.
flutter: getInstructors
[VERBOSE-2:ui_dart_state.cc(148)] Unhandled Exception: RPCError: got code -32000 with msg "invalid sender".
#0      JsonRPC.call (package:web3dart/src/io/jsonrpc.dart:47:4)
<asynchronous suspension>
#1      Web3Client._makeRPCCall (package:web3dart/src/web3client.dart:29:30)
<asynchronous suspension>
#2      Web3Client.sendRawTransaction (package:web3dart/src/web3client.dart:184:10)
#3      FinalizedTransaction.send.<anonymous closure> (package:web3dart/src/transaction.dart:115:18)
#4      _rootRunUnary (dart:async/zone.dart:1132:38)
#5      _CustomZone.runUnary (dart:async/zone.dart:1029:19)
#6      _FutureListener.handleValue (dart:async/future_impl.dart:126:18)
#7      Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:639:45)
#8      Future._propagateToListeners (dart:async/future_impl.dart:668:32)
#9      Future._complete (dart:async/future_impl.dart:473:7)
#10     _SyncCompleter.complete (dart:async/future_impl.dart:51:12)
#11     _AsyncAwaitCompleter.complete (dart:a<…>

with re writing the code this way

void _getSmartCoursesContract() async {
    String courseContractABI = json.encode(coursesABI);
    String tokenAddress = "0x8b373636Ff3147FCc01B40b57114eC282e77E2df";
    String privateKeyHex =
        "79FD6A46633A4AF7B1C3C1CA1D5EDFEC35245D42A5A3661B29AFFD8263E32853";

    var httpClient = new Client();
    var ethClient = new Web3Client(apiUrl, httpClient);
    var credentials = Credentials.fromPrivateKeyHex(privateKeyHex);

    var contractHelloABI =
        ContractABI.parseFromJSON(courseContractABI, "Courses");
    var coursesContract = new DeployedContract(contractHelloABI,
        new EthereumAddress(tokenAddress), ethClient, credentials);

    var getCoursesFn =
        coursesContract.findFunctionsByName("countInstructors").first;
    var listTokenFn = coursesContract.functions;

    print(listTokenFn[1].name);

//    for (FunctionParameter fp in getCoursesFn.parameters) {
//      print("Function ${getCoursesFn.name} ---- Parameters ${fp.name}");
//    }
//
//    for (int a = 0; a < listTokenFn.length; a++) {
//      print(listTokenFn[a].name);
//    }

//    for (ContractFunction fn in listTokenFn) {
//      for (FunctionParameter fp in fn.parameters) {
//        print("Function ${fn.name} ---- Parameters ${fp.name}");
//      }
//    }

    var age = new BigInt.from(24);
    var coursesResponse =
        new Transaction(keys: credentials, maximumGas: 127489);
    FinalizedTransaction finalizedTransaction = coursesResponse.prepareForCall(
      coursesContract,
      listTokenFn[2],
      [credentials.address.number, "Maugost", "Okore", age],
    );

    await finalizedTransaction.send(ethClient).then((result) {
      print(result);
    });
  }

Please how to i resolve this and also

  1. How do i read a method from a smart contract 2.How do i write to a method from a smart contract

mtellect avatar Feb 24 '19 11:02 mtellect

As for getting ether balance on the address I can it works perfectly well..

mtellect avatar Feb 24 '19 12:02 mtellect

it works fine when i specify a chaidID

void _getSmartCoursesContract() async {
    String courseContractABI = json.encode(coursesABI);
    String privateKeyHex =
        "79FD6A46633A4AF7B1C3C1CA1D5EDFEC35245D42A5A3661B29AFFD8263E32853";

    var httpClient = new Client();
    var ethClient = new Web3Client(apiUrl, httpClient);
    var credentials = Credentials.fromPrivateKeyHex(privateKeyHex);

    var contractABI = ContractABI.parseFromJSON(courseContractABI, "Courses");
    var coursesContract = new DeployedContract(
        contractABI,
        new EthereumAddress(credentials.address.toString()),
        ethClient,
        credentials);

    var listTokenFn = coursesContract.functions;

    ContractFunction getInstructor = listTokenFn[0];
    ContractFunction getInstructors = listTokenFn[1];
    ContractFunction setInstructor = listTokenFn[2];
    ContractFunction countInstructor = listTokenFn[3];
    ContractFunction instructorAccounts = listTokenFn[4];

    //setting  instructors

    var age = new BigInt.from(24);
    var setInstructorResponse =
        new Transaction(keys: credentials, maximumGas: 127489);
    FinalizedTransaction setInstructorTransaction =
        setInstructorResponse.prepareForCall(
      coursesContract,
      setInstructor,
      [credentials.address.number, "Maugost", "Okore", age],
    );
    await setInstructorTransaction.send(ethClient, chainId: 4).then((result) {
      print(result);
    });

    //getting instructor

    var getInstructorResponse =
        new Transaction(keys: credentials, maximumGas: 127489);
    FinalizedTransaction getInstructorTransaction =
        getInstructorResponse.prepareForCall(
      coursesContract,
      getInstructor,
      [credentials.address.number],
    );
    await getInstructorTransaction.send(ethClient, chainId: 4).then((result) {
      print(result);
    });
  }

image

Transactions are being recorded on ether scan but now i get a little confuse sir..please what's the difference between the call(), and the send() in finalized transactions? Please help??

mtellect avatar Feb 24 '19 12:02 mtellect

Hm that's weird, perhaps the first issue (transactions only work when specifying a chain id) is related to #28, I'll have to look into that some more. To answer your questions on whether to use call or send:

  • Use call for functions that don't write data to the Blockchain. These functions either have pure or view set in their stateMutability field in the contract ABI.
  • Basically, use send for all others. Calling methods with send will create a transaction that can write state and will show up on etherscan as they should.

simolus3 avatar Feb 24 '19 18:02 simolus3