Web 应用程序无法与通过 Truffle 部署的以太坊智能合约正确通信

问题描述

我在 solidity 中编写了这个非常简单的智能合约,它允许用户将待办事项添加到他们的个人列表中,获取他们的待办事项列表等等。

pragma solidity ^0.8.0;

contract Todo {

    struct Task {

        string content;
        bool completed;
    }

    mapping(address => Task[]) private tasks;

    function addTask(string memory content) public {

        tasks[msg.sender].push(Task(content,false));
    }

    function changeTaskState(uint256 taskId) public {

        tasks[msg.sender][taskId].completed = !tasks[msg.sender][taskId].completed;
    }

    function editTaskContent(uint256 taskId,string memory content) public {

        tasks[msg.sender][taskId].content = content;
    }

    function getTasks() public view returns(Task[] memory) {

        return tasks[msg.sender];
    }
}

当通过 Truffle 部署并在 Truffle(develop) 终端中测试时,这完全符合预期:

truffle(develop)> const todo = await Todo.deployed()
undefined
truffle(develop)> todo.getTasks()
[]
truffle(develop)> todo.addTask("Hello,world!")
{
  tx: '0x7e607352c1ab8f6532c5b43e282eb20f29d5bfa451dfbb873bac3506df00cb1a',receipt: {
    transactionHash: '0x7e607352c1ab8f6532c5b43e282eb20f29d5bfa451dfbb873bac3506df00cb1a',transactionIndex: 0,blockHash: '0x98b361190eadf1905c3e15b5054aa4ace8eaa33a2b4d35898f78e2165ea996a2',blockNumber: 5,from: '0x3455100c0b0617afbf0f53db5e5c07366e20791b',to: '0x645a78fe8eb3529291ba63a8e420d26c7baf61a0',gasUsed: 66634,cumulativeGasUsed: 66634,contractAddress: null,logs: [],status: true,logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',rawLogs: []
  },logs: []
}
truffle(develop)> todo.changeTaskState(0)
{
  tx: '0xddb313978411cd3f1429f1eb61b9bbde816e3a874d765aa5588a69508d226b44',receipt: {
    transactionHash: '0xddb313978411cd3f1429f1eb61b9bbde816e3a874d765aa5588a69508d226b44',blockHash: '0xbae43abf22ca06de65a41e3e54493ad944f4b997b12a3ed407bc5f778d00a3be',blockNumber: 6,gasUsed: 45320,cumulativeGasUsed: 45320,logs: []
}
truffle(develop)> todo.getTasks()
[
  [ 'Hello,world!',true,content: 'Hello,completed: true ]
]

但是,当我尝试从 Web 应用程序调用这些合约的函数时,似乎与 Truffle 提供的本地区块链存在某种通信错误

当然,我已经在浏览器中安装了 Metamask 并将其连接到 http://127.0.0.1:9545(正如 Truffle 在执行 truffle develop 命令时告诉我的那样)。我还导入了 Truffle 提供的私人短语,以便我可以访问该本地网络上的 10 个测试地址。

我还在 build/contracts 目录中找到了合约的地址和 ABI,并在 React 中设置了一个简单的前端。

import Web3 from 'web3';
import React,{ useState,useEffect } from "react";
 
const Todo_ABI = 
[
    {
      "inputs": [
        {
          "internalType": "string","name": "content","type": "string"
        }
      ],"name": "addTask","outputs": [],"stateMutability": "nonpayable","type": "function"
    },{
      "inputs": [
        {
          "internalType": "uint256","name": "taskId","type": "uint256"
        }
      ],"name": "changeTaskState","type": "uint256"
        },{
          "internalType": "string","name": "editTaskContent",{
      "inputs": [],"name": "getTasks","outputs": [
        {
          "components": [
            {
              "internalType": "string","type": "string"
            },{
              "internalType": "bool","name": "completed","type": "bool"
            }
          ],"internalType": "struct Todo.Task[]","name": "","type": "tuple[]"
        }
      ],"stateMutability": "view","type": "function","constant": true
    }
  ];

const Todo_ADDRESS = "0x645a78fe8eb3529291ba63a8e420d26c7baf61a0";

function ChangeTaskStateButton(props) {

  return (
    <button onClick={ () => props.contract.methods.changeTaskState(props.id).call() }>{ props.state }</button>
  );
}

function Task(props) {

  return (
    <li>
      { props.content } | <ChangeTaskStateButton contract={ props.contract } id={ props.id } state={ props.completed ? "Completed" : "Pending "}></ChangeTaskStateButton>
    </li>
  );
}

function TasksList(props) {

  let tasks = [];
  const tasksData = props.tasks;

  for(let i = 0; i < tasksData.length; i++) {

    tasks.push(<Task id={i} content={ tasksData[i].content } completed={ tasksData[i].completed } contract={ props.contract }></Task>);
  }

  return (
    <div>
      <ul>
        { tasks }
      </ul>
    </div>
  );
}

function TaskForm(props) {

  const [content,setContent] = useState("");

  const handleSubmit = (event) => {

    event.preventDefault();
    props.contract.methods.addTask(content).call()
      .then(() => props.setTasks(props.tasks.concat({content: content,completed: false})));
  };

  const handleChange = (event) => {

    setContent(event.target.value);
  };

  return(
    <form onSubmit={ handleSubmit }>
      <input type="text" onChange={ handleChange }></input>
      <button type="submit">Submit</button>
    </form>
  );
}

function App() {

  const [web3] = useState(new Web3(Web3.givenProvider || "http://localhost:9545"));
  const [contract] = useState(new web3.eth.Contract(Todo_ABI,Todo_ADDRESS));
  const [tasks,setTasks] = useState([]);

  useEffect(() => {

    contract.methods.getTasks().call()
      .then(tasks => {

        setTasks(tasks);
      });

  },[contract.methods]);

  return (
    <div>
      <TaskForm contract={contract} setTasks={setTasks} tasks={tasks}></TaskForm>
      <TasksList tasks={tasks} contract={contract}></TasksList>
    </div>
  );
}

getTasks()调用总是返回一个空数组,即使我通过终端添加一个Metamask 当前使用的地址相同的任务,而对 addTask()调用不存储智能合约地图中的任何内容。对这两个函数调用不会导致浏览器控制台中出现任何错误或警告。但是,对 changeTaskState()调用确实会导致显示两个错误

inpage.js:1 MetaMask - RPC Error: Internal JSON-RPC error. 
{code: -32603,message: "Internal JSON-RPC error.",data: {…}}
code: -32603
data: {message: "VM Exception while processing transaction: revert",code: -32000,data: {…}}
message: "Internal JSON-RPC error."
__proto__: Object



index.js:50 Uncaught (in promise) Error: Internal JSON-RPC error.
{
  "message": "VM Exception while processing transaction: revert","code": -32000,"data": {
    "0x359c33ac64b2b3eb0096b40b2d225679d4212f40fc86ef938af49fcc47159f2c": {
      "error": "revert","program_counter": 994,"return": "0x4e487b710000000000000000000000000000000000000000000000000000000000000032"
    },"stack": "RuntimeError: VM Exception while processing transaction: revert\n    at Function.RuntimeError.fromresults (C:\\Users\\gianm\\AppData\\Roaming\\npm\\node_modules\\truffle\\build\\webpack:\\node_modules\\ganache-core\\lib\\utils\\runtimeerror.js:94:1)\n    at C:\\Users\\gianm\\AppData\\Roaming\\npm\\node_modules\\truffle\\build\\webpack:\\node_modules\\ganache-core\\lib\\blockchain_double.js:568:1","name": "RuntimeError"
  }
}
    at Object._fireError (index.js:50)
    at sendTxCallback (index.js:540)
    at cb (util.js:689)
    at callbackifyOnRejected (util.js:666)
    at Item.push../node_modules/process/browser.js.Item.run (browser.js:153)
    at drainQueue (browser.js:123)

我也尝试使用 Ganache,而不是 Truffle 的内置本地区块链,我什至尝试更改浏览器,但似乎没有任何效果。我还检查了 Metamask 是否真的连接到了 webapp,果然是这样。我在这里错过了什么?

解决方法

与智能合约交互有两种主要方式。一个 call(只读,免费)和一个 transaction(读写,需要 Gas 费)。

您的 React 代码使用 .call()addTask()changeTaskState() 方法,这不允许写入合约存储。


由于您正在使用 MetaMask,您应该使用 Ethereum Provider API 并向 MM 提交一个 request,要求用户确认交易。

因此,您可以获取 props.contract.methods.addTask(content).call() 字段的内容,然后生成交易请求,而不是 data

const data = props.contract.methods.addTask(content).encodeABI();

ethereum.request({
    method: 'eth_sendTransaction',params: [{
        from: senderAddress,to: contractAddress,data: data
    }],});

注意:您可以在 connecting to MM 之后设置 senderAddress

另一种方法是使用 web3 .send() 方法,但这需要将发送者的私钥传递给 web3(前端 JS 应用程序中的坏主意)或解锁的提供者帐户(本地提供者通常几乎没有解锁帐户,但生产帐户没有)。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...