簡體   English   中英

Web 應用程序無法與通過 Truffle 部署的以太坊智能合約正確通信

[英]Web Application doesn't communicate correctly with Ethereum smart contract deployed through 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',
    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 ]
]

但是,當我嘗試從 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",
      "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>
  );
}

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,果然是這樣。 我在這里缺少什么?

與智能合約交互有兩種主要方式。 一個調用(只讀,免費)和一個交易(讀寫,需要gas費用)。

您的反應代碼將addTask() .call()方法用於addTask()changeTaskState() ,這不允許寫入合約存儲。


由於您正在使用 MetaMask,您應該使用 Ethereum Provider API 並向 MM 提交 請求,要求用戶確認交易。

因此,您可以獲取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
    }],
});

注意:您可以在連接到 MM后設置senderAddress

另一種方法是使用 web3 .send()方法,但這需要將發送者的私鑰傳遞給web3 (前端 JS 應用程序中的壞主意)或未鎖定的提供者帳戶(本地提供者通常很少有未鎖定的帳戶,但生產帳戶)別)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM