简体   繁体   English

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

[英]Web Application doesn't communicate correctly with Ethereum smart contract deployed through Truffle

I coded this really simple smart contract in Solidity which allows users to add todo tasks to their personal list, to fetch their list of todos, and so on.我在 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];
    }
}

This works exactly as intended when deployed through Truffle and tested in the Truffle(develop) terminal:当通过 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',
    transactionIndex: 0,
    blockHash: '0xbae43abf22ca06de65a41e3e54493ad944f4b997b12a3ed407bc5f778d00a3be',
    blockNumber: 6,
    from: '0x3455100c0b0617afbf0f53db5e5c07366e20791b',
    to: '0x645a78fe8eb3529291ba63a8e420d26c7baf61a0',
    gasUsed: 45320,
    cumulativeGasUsed: 45320,
    contractAddress: null,
    logs: [],
    status: true,
    logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
    rawLogs: []
  },
  logs: []
}
truffle(develop)> todo.getTasks()
[
  [ 'Hello, world!', true, content: 'Hello, world!', completed: true ]
]

However, when I try to call these contract's functions from a web-app, it seems like there are some kind of communication errors with the local blockchain provided by Truffle.但是,当我尝试从 Web 应用程序调用这些合约的功能时,似乎与 Truffle 提供的本地区块链存在某种通信错误。

Of course I've installed Metamask in my browser and I've connected it to http://127.0.0.1:9545 (as Truffle tells me to do upon executing the truffle develop command).当然,我已经在浏览器中安装了 Metamask,并将其连接到http://127.0.0.1:9545 (正如 Truffle 在执行truffle develop命令时告诉我的那样)。 I've also imported the private phrase provided by Truffle, so that I could access the 10 test addresses on that local network.我还导入了 Truffle 提供的私人短语,以便我可以访问该本地网络上的 10 个测试地址。

I've also found the contract's address and ABI in the build/contracts directory and set up a simple front end in React.我还在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",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "taskId",
          "type": "uint256"
        },
        {
          "internalType": "string",
          "name": "content",
          "type": "string"
        }
      ],
      "name": "editTaskContent",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getTasks",
      "outputs": [
        {
          "components": [
            {
              "internalType": "string",
              "name": "content",
              "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>
  );
}

The call to getTasks() always returns an empty array, even if I add a task through the terminal with the same address that's currently in use on Metamask, while the call to addTask() doesn't store anything in the smart contracts's map.getTasks()的调用始终返回一个空数组,即使我通过终端添加了一个与 Metamask 当前使用的地址相同的任务,而对addTask()的调用不会在智能合约的地图中存储任何内容。 The call to these two functions don't cause any errors or warnings to appear in the browser's console.对这两个函数的调用不会导致浏览器控制台中出现任何错误或警告。 However, the call to changeTaskState() does cause two errors do be displayed:但是,对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)

I've also tried to use Ganache, instead of Truffle's built-in local blockchain, and I even tried to change browser, but nothing seems to work.我还尝试使用 Ganache,而不是 Truffle 的内置本地区块链,我什至尝试更改浏览器,但似乎没有任何效果。 I've also checked whether Metamask was actually connected to the webapp and sure enough it was.我还检查了 Metamask 是否真的连接到了 webapp,果然是这样。 What am I missing here?我在这里缺少什么?

There are two main ways to interact with a smart contract.与智能合约交互有两种主要方式。 A call (read-only, free) and a transaction (read-write, requires gas fees).一个调用(只读,免费)和一个交易(读写,需要gas费用)。

Your react code uses the .call() method for addTask() and changeTaskState() , which doesn't allow writing in the contract storage.您的反应代码将addTask() .call()方法用于addTask()changeTaskState() ,这不允许写入合约存储。


Since you're working with MetaMask, you should use the Ethereum Provider API and submit a request to MM that will ask the user to confirm the transaction.由于您正在使用 MetaMask,您应该使用 Ethereum Provider API 并向 MM 提交 请求,要求用户确认交易。

So instead of the props.contract.methods.addTask(content).call() you can get the contents of the data field and then generate the transaction request.因此,您可以获取data字段的内容,然后生成交易请求,而不是props.contract.methods.addTask(content).call()

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

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

Note: You can set the senderAddress after connecting to MM .注意:您可以在连接到 MM后设置senderAddress

Another way is to use the web3 .send() method, but this requires passing the private key of the sender to web3 (bad idea in a frontend JS app) or unlocked provider account (local providers usually have few unlocked accounts, but production ones don't).另一种方法是使用 web3 .send()方法,但这需要将发送者的私钥传递给web3 (前端 JS 应用程序中的坏主意)或未锁定的提供者帐户(本地提供者通常很少有未锁定的帐户,但生产帐户)别)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM