{"content":{"title":"ChatGPT Solidity 智能合约","body":"# 摘要\r\nOpenAI 的最新版本 GPT-3.5 或 ChatGPT 为语言翻译、文本摘要和文本生成等任务的自然语言处理提供了显着改进。开发人员开始探索请求 ChatGPT 协助编写、审查或重构代码以及为开发任务提供技术信息的潜力。然而，在进行测试时，ChatGPT 很难从头开始编写 Solidity 代码，并且很难理解其他人编写的 Solidity 代码，即使有经验丰富的开发人员的帮助也是如此。在 ChatGPT 成为编写代码的宝贵工具之前，需要更多的数据和计算能力。\r\n\r\n此摘要由 GPT-3.5 生成\r\n\r\n# 深入探讨 ChatGPT-3.5\r\n您可能已经注意到 OpenAI 最近发布了其 GPT 模型的更新版本。最新版本 GPT-3.5，也称为 ChatGPT，比其前身有显着改进，包括更高的准确性和更大的容量。这些改进使 GPT-3.5 在自然语言处理任务（例如语言翻译、文本摘要和文本生成）方面更加出色。\r\n\r\n社交媒体充斥着人工智能生成的博客文章、文章，甚至是根据 ChatGPT 提示创建的诗歌和食谱。但值得一提的是，开发人员开始意识到请求 ChatGPT 帮助编写、审查甚至重构代码以及提供有助于完成开发任务的技术信息的可能性。\r\n\r\n在这篇文章中，我想了解对 Web3 开发人员的影响。我将重点关注两个方面：首先，在没有先前知识的情况下编写 Solidity，其次是更具挑战性的任务——在有一定理解的情况下编写 Solidity，并从前端与智能合约进行交互。\r\n\r\n# 在没有任何经验知识的情况下编写 Solidity\r\n让我们从第一个提示开始：“编写一个NTF售卖智能合约”。\r\n以下是 GPT 给我的回应：\r\n\r\n```\r\npragma solidity ^0.6.0;\r\n\r\ncontract TokenSale {\r\n\r\n  // The name of the token\r\n  string public name = \"MyToken\";\r\n\r\n  // The symbol of the token\r\n  string public symbol = \"MTK\";\r\n\r\n  // The total supply of tokens\r\n  uint256 public totalSupply;\r\n\r\n  // The amount of tokens sold in the current sale\r\n  uint256 public totalSold = 0;\r\n\r\n  // The price of each token in wei\r\n  uint256 public tokenPrice;\r\n\r\n  // The address of the contract owner\r\n  address public owner;\r\n\r\n  // Events for token purchase and refund\r\n  event TokenPurchase(address indexed purchaser, uint256 amount);\r\n  event TokenRefund(address indexed refundee, uint256 amount);\r\n\r\n  // Mapping from user address to their token balance\r\n  mapping(address => uint256) public balanceOf;\r\n\r\n  // Constructor to set the total supply of tokens and the price per token\r\n  constructor(uint256 _totalSupply, uint256 _tokenPrice) public {\r\n    owner = msg.sender;\r\n    totalSupply = _totalSupply;\r\n    tokenPrice = _tokenPrice;\r\n  }\r\n\r\n  // Fallback function to purchase tokens\r\n  function() external payable {\r\n    buyTokens();\r\n  }\r\n\r\n  // Function to purchase tokens\r\n  function buyTokens() public payable {\r\n    require(msg.value >= tokenPrice, \"Insufficient payment\");\r\n\r\n    // Calculate the number of tokens to be purchased\r\n    uint256 tokens = msg.value.div(tokenPrice);\r\n\r\n    // Update total sold and the balance of the purchaser\r\n    totalSold += tokens;\r\n    balanceOf[msg.sender] += tokens;\r\n\r\n    // Emit a token purchase event\r\n    emit TokenPurchase(msg.sender, tokens);\r\n  }\r\n\r\n  // Function to refund ether to the sender\r\n  function refund() public {\r\n    require(balanceOf[msg.sender] == 0, \"Cannot refund - tokens already purchased\");\r\n    msg.sender.transfer(msg.value);\r\n    emit TokenRefund(msg.sender, msg.value);\r\n  }\r\n\r\n  // Function to end the token sale\r\n  function endSale() public {\r\n    require(msg.sender == owner, \"Only the owner can end the sale\");\r\n    require(totalSold == totalSupply, \"Cannot end sale - all tokens not sold\");\r\n    owner.transfer(address(this).balance);\r\n  }\r\n}\r\n```\r\n我有兴趣注意到它使用了 Solidity ^0.6.0 ，而且它没有选择使用 ERC721 和 ERC1155 标准。\r\n\r\n我决定尝试一个实验。我第二次给了它同样的提示——令我惊讶的是，这次它生成了一个使用 ERC721 标准的合约。这表明该系统如何具有随机方面来保持创新向前发展。\r\n\r\n```\r\n// Import ERC-721 interfaces\r\nimport \"https://github.com/OpenZeppelin/openzeppelin-solidity/contracts/token/ERC721/ERC721.sol\";\r\n\r\ncontract NFTokenSale is ERC721 {\r\n  // ...\r\n}\r\n```\r\n仍然以对 Solidity 或智能合约一无所知的人，输入提示“我可以怎么测试这段代码？” 并得到以下答案：\r\n\r\n```\r\n您可以使用本地开发环境或区块链测试网测试此代码。\r\n要在本地测试代码，您可以使用 Solidity 编译器solc来编译代码并生成合约 ABI（应用程序二进制接口）。然后，您可以使用本地以太坊客户端ganache-cli将合约部署到本地开发区块链，并使用 web3.js 库与其交互truffle-contract。\r\n要在区块链测试网上测试代码，您可以使用 Remix 或 Bamboo 等服务来编译代码并生成合约 ABI。然后，您可以使用 MetaMask 等钱包将合约部署到 Rinkeby 或 Ropsten 等测试网，并使用 web3.js 库与其交互。\r\n或者，您可以使用 OpenZeppelin 的 Ethereum Package Registry 等服务将合约的预编译版本部署到测试网，并使用 web3.js 库与其交互。与自己编译和部署合约相比，这种方式可以节省您的时间和精力。\r\n```\r\n虽然这具有广泛的帮助，但它实际上并不包含任何可操作的项目。我决定继续尝试使用 Hardhat 编写代码——但编译失败，因为指定版本的 OpenZeppelin 没有 ERC721 合约pragma solidity ^0.6.0;\r\n\r\n为了弄清楚 ChatGPT 的逻辑，我询问了如何解决这个问题，助手返回了一个声明，明确表明它是在旧数据集上工作：\r\n\r\n```\r\n您可以通过指定最新版本的 OpenZeppelin 库支持的 Solidity 版本来解决此问题。OpenZeppelin 库的当前版本支持 Solidity 版本 0.5.x 和 0.6.x，因此您可以更新合约中的 pragma 语句以使用这些版本之一：\r\n// For Solidity 0.5.x\r\npragma solidity ^0.5.0;\r\n// For Solidity 0.6.x\r\npragma solidity ^0.6.0;\r\n您还应该确保您在合同中使用的是最新版本的 OpenZeppelin 库。您可以通过使用 import 语句从最新版本的库导入合约接口来执行此操作，该库托管在 Ethereum Package Registry 上。例如：\r\n\r\n// Import ERC-721 interfaces from the latest version of the OpenZeppelin library\r\nimport \"@openzeppelin/contracts/token/ERC721/ERC721.sol\";\r\n\r\n```\r\n最后一部分很关键，因为如果不更改到以太坊包注册表的 http 导入，Hardhat 将无法编译。\r\n我决定改变任务并问 ChatGPT：“NFTTokenSale 合约是否编译？” 只收到以下通用且令人沮丧的答案：\r\n\r\n```\r\n在不实际尝试编译 NFTokenSalecontract 的情况下，无法确定它是否会编译。合约可能包含语法错误或其他阻止其成功编译的问题。\r\n\r\n```\r\n暂时放弃助手，我尝试自己编译，但我开始收到各种编译错误，包括：\r\n\r\na.回退功能不是通过使用fallback关键字实现的\r\nb.所有公共变量都没有正确实现\r\nc.字符串连接不像 Solidity 0.6.x 那样工作\r\n这让我很清楚，对于没有 Solidity 知识的用户或非开发人员来说，ChatGPT 还没有达到可以发挥作用的阶段。\r\n现在是时候继续看看 ChatGPT 如何充当具有 Solidity 应用知识的开发人员的助手了。\r\n\r\n# 使用 AI 协助经验丰富的开发人员编写 Solidity\r\n这次我决定从现有的东西开始，比如 CryptoPunk合约。我选择这个是因为它很短，有助于解决 ChatGPT 超时问题。\r\n\r\n我首先尝试通过助手来理解部分代码（整个合约在 ChatGPT 上返回超时）：“解释下面的 buyPunk 方法”\r\n这产生了以下解释：\r\n\r\n```\r\nbuyPunk() 函数允许用户从另一个提供出售的用户那里购买“punk”代币。此函数将 punk 的索引作为输入，并且期望想要购买 punk 的用户在函数调用的同时发送以eth付款。该函数首先检查punk是否真的在出售，以及是否允许调用者购买它（如果它已被提供给特定用户出售）。如果这些检查中的任何一个失败，函数就会抛出错误并停止执行。如果检查通过，该函数会通过将 punk 从卖方转移到买方来更新 punk 的所有权。它还会更新买卖双方的余额以反映转账。最后，该函数会检查买家之前是否为punk出价，如果是，它退还出价并从记录中删除该出价。这样做是为了确保买家最终不会为punk支付两次费用。\r\n\r\n```\r\n这很有趣，因为它为我提供了很多乍一看看起来很抽象的东西的上下文。我发现特别酷的是它能够理解的含义`BalanceOf[seller]—`和`BalanceOf[msg.sender]++`无需利用代码中的任何注释。\r\n\r\n然后我尝试通过给出以下提示来更改代码：“更改上述代码以从交易中收取 5% 的费用并将其保留在合同中”。\r\n\r\n```\r\n// Calculate the 5% fee and keep it in the contract\r\nuint fee = msg.value / 20;\r\naddress contractAddress = this;\r\ncontractAddress.transfer(fee);\r\n// few lines later after other logic ...\r\npendingWithdrawals[seller] += msg.value - fee;\r\n```\r\n我印象深刻，但我不得不再一次大大缩小查询范围以避免超时，即使这样，我也无法编写整个函数。\r\n\r\n现在我要求它测试功能：\r\n\r\n```\r\nfunction testBuyPunk() {\r\n    // Create a new CryptoPunksMarket contract\r\n    CryptoPunksMarket contract = new CryptoPunksMarket();\r\n\r\n    // Set up some test data\r\n    address seller = msg.sender;\r\n    address buyer = address(1);\r\n    uint punkIndex = 0;\r\n    uint value = 5;\r\n\r\n    // Offer the punk for sale\r\n    contract.offerPunkForSale(seller, punkIndex, value, 0x0);\r\n\r\n    // Buy the punk from the seller\r\n    contract.buyPunk(punkIndex).send({ value: value, from: buyer });\r\n\r\n    // Check that the punk has been transferred from the seller to the buyer\r\n    assert(contract.balanceOf(seller) == 0);\r\n    assert(contract.balanceOf(buyer) == 1);\r\n    assert(contract.punkIndexToAddress(punkIndex) == buyer);\r\n}\r\n```\r\n我认为这是一种有趣的方法，尽管它没有考虑到费用。ChatGPT 的一个很酷的地方是它保留了你之前提示的知识，允许你通过让助手执行后续任务来迭代和完善。\r\n\r\n我指出：“它不检查已扣除 5% 费用的事实”并收到以下答复：\r\n\r\n```\r\nfunction testBuyPunk() {\r\n    // Create a new CryptoPunksMarket contract\r\n    CryptoPunksMarket contract = new CryptoPunksMarket();\r\n\r\n    // Set up some test data\r\n    address seller = msg.sender;\r\n    address buyer = address(1);\r\n    uint punkIndex = 0;\r\n    uint value = 5;\r\n\r\n    // Offer the punk for sale\r\n    contract.offerPunkForSale(seller, punkIndex, value, 0x0);\r\n\r\n    // Buy the punk from the seller\r\n    contract.buyPunk(punkIndex).send({ value: value, from: buyer });\r\n\r\n    // Check that the 5% fee has been deducted from the transaction and kept in the contract\r\n    uint fee = value / 20;\r\n    assert(address(this).balance == fee);\r\n\r\n    // Check that the punk has been transferred from the seller to the buyer\r\n    assert(contract.balanceOf(seller) == 0);\r\n    assert(contract.balanceOf(buyer) == 1);\r\n    assert(contract.punkIndexToAddress(punkIndex) == buyer);\r\n```\r\n它试图检查合同是否有正确的余额，但语法错误，因为它提到了这一点——而且，卖方无法获得所有到期的资金。于是我提示：扣费卖家不能收到全款。\r\n\r\n```\r\nfunction testBuyPunk() {\r\n// Create a new CryptoPunksMarket contract\r\nCryptoPunksMarket contract = new CryptoPunksMarket();\r\n\r\n// Set up some test data\r\naddress seller = msg.sender;\r\naddress buyer = address(1);\r\nuint punkIndex = 0;\r\nuint value = 5;\r\n\r\n// Offer the punk for sale\r\ncontract.offerPunkForSale(seller, punkIndex, value, 0x0);\r\n\r\n// Buy the punk from the seller\r\ncontract.buyPunk(punkIndex).send({ value: value, from: buyer });\r\n\r\n// Check that the seller receives the correct amount after the 5% fee has been deducted\r\nuint fee = value / 20;\r\nassert(contract.pendingWithdrawals(seller) == value - fee);\r\n\r\n// Check that the punk has been transferred from the seller to the buyer\r\nassert(contract.balanceOf(seller) == 0);\r\nassert(contract.balanceOf(buyer) == 1);\r\nassert(contract.punkIndexToAddress(punkIndex) == buyer);\r\n}\r\n```\r\n令人沮丧的是，pendingWithdrawals现在已经考虑了费用，但现在不再检查合同的余额。\r\n\r\n从这里我们可以看出，我们确实可以通过提示 ChatGPT 来成功更新代码，但是性能限制（特别是在超时方面）意味着目前将其用于复杂的用例并不实用。\r\n\r\n# 从前端与 Solidity 智能合约交互\r\n\r\n我的最后一个挑战是了解如果我不使用 Solidity 而改用 Javascript 与现有智能合约交互会发生什么。为此，我需要获取合约的 ABI，将 Solidity 转换为我的前端可以使用的 JSON 格式。\r\n\r\n我提示 ChatGPT “编写 transferPunk 函数的 ABI”，它运行良好，包括以人类可读的格式解释函数的特性。\r\n下面的一个片段：\r\n\r\n```\r\n{\r\n  \"constant\": false,\r\n  \"inputs\": [\r\n    {\r\n      \"name\": \"_punkIndex\",\r\n      \"type\": \"uint256\"\r\n    }\r\n  ],\r\n  \"name\": \"buyPunk\",\r\n  \"outputs\": [],\r\n  \"payable\": true,\r\n  \"stateMutability\": \"payable\",\r\n  \"type\": \"function\"\r\n}\r\n```\r\n该buyPunk函数未标记为constant，因此它可以修改合约的状态。它需要一个输入 ，_punkIndex它是要购买的punk的索引。功能是payable，因此它可以接收加密货币作为付款。它没有返回值，所以outputs数组是空的。\r\n\r\n请注意，这只是该buyPunk函数的一种可能的 ABI。具体的ABI将取决于智能合约中功能的实现。\r\n我发现的唯一小问题是 ABI 格式将所有函数包装到一个数组中，而对于此代码段它没有这样做。然而，当我要求它生成整个合约的 ABI 时，它确实正确地将所有函数封装到一个数组中。\r\n\r\n下一步是查看它在前端的表现如何。我提示：“用 React 和 etherjs 写一个带有小按钮的最小 ui 来购买punk”\r\n\r\n下面的一个片段：\r\n\r\n```\r\nimport React, { useState } from \"react\";\r\nimport ethers from \"ethers\";\r\n\r\n// Set the contract ABI and address\r\nconst contractABI = [\r\n  {\r\n    \"constant\": false,\r\n    \"inputs\": [\r\n      {\r\n        \"name\": \"_punkIndex\",\r\n        \"type\": \"uint256\"\r\n      }\r\n    ],\r\n    \"name\": \"buyPunk\",\r\n    \"outputs\": [],\r\n    \"payable\": true,\r\n    \"stateMutability\": \"payable\",\r\n    \"type\": \"function\"\r\n  },\r\n  // Other contract ABI goes here...\r\n];\r\nconst contractAddress = \"0x...\";\r\n\r\nfunction App() {\r\n  // Create a state variable to store the status of the transaction\r\n  const [transactionStatus, setTransactionStatus] = useState(\"\");\r\n\r\n  // Function to handle the buy punk button click\r\n  async function handleBuyPunkClick() {\r\n    // Create a provider to connect to the Ethereum network\r\n    const provider = new ethers.providers.JsonRpcProvider(\"http://localhost:8545\");\r\n\r\n    // Create a wallet to sign transactions\r\n    const privateKey = \"0x...\";\r\n    const wallet = new ethers.Wallet(privateKey, provider);\r\n\r\n    // Get the contract instance\r\n    const contract = new ethers.Contract(contractAddress, contractABI, wallet);\r\n\r\n    // Buy a punk by calling the buyPunk function and passing the punk index and the payment amount\r\n    // The payment amount should be in wei\r\n    const punkIndex = 0;\r\n    const paymentAmount = ethers.utils.parseEther(\"0.01\");\r\n    try {\r\n      await contract.buyPunk(punkIndex, { value: paymentAmount });\r\n      setTransactionStatus(\"Transaction successful\");\r\n    } catch (err) {\r\n      setTransactionStatus(`Transaction failed: ${err.message}`);\r\n    }\r\n  }\r\n\r\n  return (\r\n    <div>\r\n      <button onClick={handleBuyPunkClick}>Buy Punk</button>\r\n      <p>{transactionStatus}</p>\r\n```\r\n这个例子相当不错，但正如您所看到的那样，它在整个过程中被打断了一部分，并且没有任何解释来说明它在做什么。\r\n\r\n尽管如此，令人印象深刻的是，ChatGPT 可以处理上下文，以便它记住它生成的 ABI，然后使用它来构建带有 React 和 etherjs 的示例。\r\n\r\n我现在不得不重置该部分并从头开始。有趣的是，这次的结果大不相同，我不得不提示几次以重新创建正确的上下文，但最终还是产生了一些东西。\r\n\r\n```\r\nimport React, { useState } from 'react';\r\nimport { useContract, useWeb3 } from '@ethers-react/web3';\r\n\r\nconst buyPunkABI = [\r\n  {\r\n    constant: false,\r\n    inputs: [\r\n      {\r\n        name: 'punkIndex',\r\n        type: 'uint256',\r\n      },\r\n      {\r\n        name: 'onlyBuyFrom',\r\n        type: 'address',\r\n      },\r\n    ],\r\n    name: 'buyPunk',\r\n    outputs: [],\r\n    payable: true,\r\n    stateMutability: 'payable',\r\n    type: 'function',\r\n  },\r\n];\r\n\r\nfunction App() {\r\n  const contract = useContract(process.env.REACT_APP_CONTRACT_ADDRESS, buyPunkABI);\r\n  const { account } = useWeb3();\r\n  const [punkIndex, setPunkIndex] = useState(0);\r\n\r\n  async function handleMint() {\r\n    await contract.mint(account, punkIndex);\r\n  }\r\n\r\n  return (\r\n    <div>\r\n      <input\r\n        type=\"number\"\r\n        value={punkIndex}\r\n        onChange={(e) => setPunkIndex(e.target.value)}\r\n      />\r\n      <button onClick={handleMint}>Buy Punk</button>\r\n    </div>\r\n  );\r\n}\r\n\r\nexport default App;\r\n```\r\n这看起来不错，但不幸的是它并不像看起来那么好——因为@ethers-react/web3它不作为一个库存在。ChatGPT 似乎只是决定根据它所见的其他人的概念和功能来发明一个库。\r\n\r\n然后我尝试了多种不同的提示，例如“仅使用 React 和 Ethers 库重写上述示例”或“ethers-react 库不存在，仅使用import ethers from \"ethers\";和使用此库”，但没有一个输出能满足我的需求。\r\n# 结论\r\n尽管结果喜忧参半，但 ChatGPT（又名 GPT-3.5）是朝着使用 AI 助手编写代码的方向迈出的一步。我真的很喜欢做这些小实验。然而，与我用 JavaScript 和其他语言进行的其他实验相比，我的努力明显得出的结论是，当涉及到 Web3 空间时，GPT 还不够准确。\r\n\r\n公平地说，与通用 JavaScript 代码相比，可用的 Solidity 和 Web3 相关 JavaScript 代码要少得多。另外，web3 行业在不断变化，这使得 ChatGPT 依赖旧数据集的问题更加严重。.\r\n\r\n从积极的方面来说，从 Solidity 生成 ABI 是它做得很好的事情，这表明它可以从可用的片段中学习一般规则来创建新的东西。\r\n\r\n我发现另一个非常有用的方面是它能够用简单的英语解释代码。我认为这是一个可以帮助开发人员更好地理解其他人编写的代码的领域，因为大多数时候，开发人员阅读而不是编写新代码。\r\n\r\n总之，尽管困难重重，我认为这项技术非常有前途，但我们还处于早期阶段，它需要更多的数据和计算能力才能实现下一个突破。使用编译器训练模型以验证输出可以显着提高编写代码时输出的准确性。\r\n\r\n我很期待看到 GPT 接下来会带来什么。让我知道您在探索 ChatGPT 和 Web3 时的发现！"},"author":{"user":"https://learnblockchain.cn/people/12138","address":null},"history":null,"timestamp":1673674158,"version":1}