Solidity编程开发实例

2023-11-08

Solidity 编程开发实例

Voting 投票

接下来的智能合约教程非常复杂,但展示了很多Solidity的特性。它实现了一个入门的投票合约。当然,电子选举的主要问题是如何赋予投票权给准确的人,并防止操纵。我们不能解决所有的问题,但至少我们会展示如何委托投票可以同时做到投票统计是自动和完全透明。

思路是为每张选票创建一个合约,每个投票选项提供一个短名称。合约创建者作为会长将会给每个投票参与人各自的地址投票权。

地址后面的人们可以选择自己投票或者委托信任的代表人替他们投票。在投票结束后,winningProposal()将会返回获得票数最多的提案。

 /// @title Voting with delegation.
 /// @title 授权投票
    contract Ballot
    {
       // 这里声明了复杂类型
         // 将会在被后面的参数使用
           // 代表一个独立的投票人。
        struct Voter
        {
            uint weight; // 累积的权重。
            bool voted;  // 如果为真,则表示该投票人已经投票。
            address delegate; // 委托的投票代表
            uint vote;   // 投票选择的提案索引号
        }

       // 这是一个独立提案的类型
        struct Proposal
        {
            bytes32 name;   // 短名称(32字节)
            uint voteCount; // 累计获得的票数
        }
    address public chairperson;
  //这里声明一个状态变量,保存每个独立地址的`Voter` 结构
    mapping(address => Voter) public voters;
    //一个存储`Proposal`结构的动态数组
    Proposal[] public proposals;

    // 创建一个新的投票用于选出一个提案名`proposalNames`.
    function Ballot(bytes32[] proposalNames)
    {
        chairperson = msg.sender;
        voters[chairperson].weight = 1;

        //对提供的每一个提案名称,创建一个新的提案
        //对象添加到数组末尾
        for (uint i = 0; i < proposalNames.length; i++)
            //`Proposal({...})` 创建了一个临时的提案对象,
            //`proposal.push(...)`添加到了提案数组`proposals`末尾。
            proposals.push(Proposal({
                name: proposalNames[i],
                voteCount: 0
            }));
    }

     //给投票人`voter`参加投票的投票权,
    //只能由投票主持人`chairperson`调用。
    function giveRightToVote(address voter)
    {
        if (msg.sender != chairperson || voters[voter].voted)
            //`throw`会终止和撤销所有的状态和以太改变。
           //如果函数调用无效,这通常是一个好的选择。
           //但是需要注意,这会消耗提供的所有gas。
            throw;
        voters[voter].weight = 1;
    }

    // 委托你的投票权到一个投票代表 `to`。
    function delegate(address to)
    {
        // 指定引用
        Voter sender = voters[msg.sender];
        if (sender.voted)
            throw;

        //当投票代表`to`也委托给别人时,寻找到最终的投票代表
        while (voters[to].delegate != address(0) &&
               voters[to].delegate != msg.sender)
            to = voters[to].delegate;
        // 当最终投票代表等于调用者,是不被允许的。
        if (to == msg.sender)
            throw;
        //因为`sender`是一个引用,
        //这里实际修改了`voters[msg.sender].voted`
        sender.voted = true;
        sender.delegate = to;
        Voter delegate = voters[to];
        if (delegate.voted)
            //如果委托的投票代表已经投票了,直接修改票数
            proposals[delegate.vote].voteCount += sender.weight;
        else
            //如果投票代表还没有投票,则修改其投票权重。
            delegate.weight += sender.weight;
    }

    ///投出你的选票(包括委托给你的选票)
    ///给 `proposals[proposal].name`。
    function vote(uint proposal)
    {
        Voter sender = voters[msg.sender];
        if (sender.voted) throw;
        sender.voted = true;
        sender.vote = proposal;
        //如果`proposal`索引超出了给定的提案数组范围
        //将会自动抛出异常,并撤销所有的改变。
        proposals[proposal].voteCount += sender.weight;
    }

   ///@dev 根据当前所有的投票计算出当前的胜出提案
    function winningProposal() constant
            returns (uint winningProposal)
    {
        uint winningVoteCount = 0;
        for (uint p = 0; p < proposals.length; p++)
        {
            if (proposals[p].voteCount > winningVoteCount)
            {
                winningVoteCount = proposals[p].voteCount;
                winningProposal = p;
            }
        }
    }
}

可能的改进

现在,指派投票权到所有的投票参加者需要许多的交易。你能想出更好的方法么?

盲拍

这一节,我们将展示在以太上创建一个完整的盲拍合约是多么简单。我们从一个所有人都能看到出价的公开拍卖开始,接着扩展合约成为一个在拍卖结束以前不能看到实际出价的盲拍。

简单的公开拍卖

通常简单的公开拍卖合约,是每个人可以在拍卖期间发送他们的竞拍出价。为了实现绑定竞拍人的到他们的拍卖,竞拍包括发送金额/ether。如果产生了新的最高竞拍价,前一个最高价竞拍人将会拿回他的钱。在竞拍阶段结束后,受益人人需要手动调用合约收取他的钱 — — 合约不会激活自己。

contract SimpleAuction {
    // 拍卖的参数。
   // 时间要么为unix绝对时间戳(自1970-01-01以来的秒数),
   // 或者是以秒为单位的出块时间
    address public beneficiary;
    uint public auctionStart;
    uint public biddingTime;

    //当前的拍卖状态
    address public highestBidder;
    uint public highestBid;

   //在结束时设置为true来拒绝任何改变
    bool ended;

   //当改变时将会触发的Event
    event HighestBidIncreased(address bidder, uint amount);
    event AuctionEnded(address winner, uint amount);

    //下面是一个叫做natspec的特殊注释,
    //由3个连续的斜杠标记,当询问用户确认交易事务时将显示。

    ///创建一个简单的合约使用`_biddingTime`表示的竞拍时间,
   /// 地址`_beneficiary`.代表实际的拍卖者
    function SimpleAuction(uint _biddingTime,
                           address _beneficiary) {
        beneficiary = _beneficiary;
        auctionStart = now;
        biddingTime = _biddingTime;
    }

    ///对拍卖的竞拍保证金会随着交易事务一起发送,
   ///只有在竞拍失败的时候才会退回
    function bid() {

       //不需要任何参数,所有的信息已经是交易事务的一部分
        if (now > auctionStart + biddingTime)
           //当竞拍结束时撤销此调用
            throw;
        if (msg.value <= highestBid)
           //如果出价不是最高的,发回竞拍保证金。
            throw;
        if (highestBidder != 0)
            highestBidder.send(highestBid);
        highestBidder = msg.sender;
        highestBid = msg.value;
        HighestBidIncreased(msg.sender, msg.value);
    }

   ///拍卖结束后发送最高的竞价到拍卖人
    function auctionEnd() {
        if (now <= auctionStart + biddingTime)
            throw; 
            //拍卖还没有结束
        if (ended)
            throw; 
     //这个收款函数已经被调用了
        AuctionEnded(highestBidder, highestBid);
        //发送合约拥有所有的钱,因为有一些保证金可能退回失败了。

        beneficiary.send(this.balance);
        ended = true;
    }

    function () {
        //这个函数将会在发送到合约的交易事务包含无效数据
        //或无数据的时执行,这里撤销所有的发送,
        //所以没有人会在使用合约时因为意外而丢钱。
        throw;
    }
}

Blind Auction 盲拍

接下来扩展前面的公开拍卖成为一个盲拍。盲拍的特点是拍卖结束以前没有时间压力。在一个透明的计算平台上创建盲拍系统听起来可能有些矛盾,但是加密算法能让你脱离困境。

在拍卖阶段, 竞拍人不需要发送实际的出价,仅仅只需要发送一个它的散列值。因为目前几乎不可能找到两个值(足够长)的散列值相等,竞拍者提交他们的出价散列值。在拍卖结束后,竞拍人重新发送未加密的竞拍出价,合约将检查其散列值是否和拍卖阶段发送的一样。 另一个挑战是如何让拍卖同时实现绑定和致盲 :防止竞拍人竞拍成功后不付钱的唯一的办法是,在竞拍出价的同时发送保证金。但是在Ethereum上发送保证金是无法致盲,所有人都能看到保证金。下面的合约通过接受任何尽量大的出价来解决这个问题。当然这可以在最后的揭拍阶段进行复核,一些竞拍出价可能是无效的,这样做的目的是(它提供一个显式的标志指出是无效的竞拍,同时包含高额保证金):竞拍人可以通过放置几个无效的高价和低价竞拍来混淆竞争对手。

contract BlindAuction
{
    struct Bid
    {
        bytes32 blindedBid;
        uint deposit;
    }
    address public beneficiary;
    uint public auctionStart;
    uint public biddingEnd;
    uint public revealEnd;
    bool public ended;

    mapping(address => Bid[]) public bids;

    address public highestBidder;
    uint public highestBid;

    event AuctionEnded(address winner, uint highestBid);

   ///修饰器(Modifier)是一个简便的途径用来验证函数输入的有效性。
   ///`onlyBefore` 应用于下面的 `bid`函数,其旧的函数体替换修饰器主体中 `_`后就是其新的函数体
    modifier onlyBefore(uint _time) { if (now >= _time) throw; _ }
    modifier onlyAfter(uint _time) { if (now <= _time) throw; _ }

    function BlindAuction(uint _biddingTime,
                            uint _revealTime,
                            address _beneficiary)
    {
        beneficiary = _beneficiary;
        auctionStart = now;
        biddingEnd = now + _biddingTime;
        revealEnd = biddingEnd + _revealTime;
    }

   ///放置一个盲拍出价使用`_blindedBid`=sha3(value,fake,secret).
   ///仅仅在竞拍结束正常揭拍后退还发送的以太。当随同发送的以太至少
   ///等于 "value"指定的保证金并且 "fake"不为true的时候才是有效的竞拍
   ///出价。设置 "fake"为true或发送不合适的金额将会掩没真正的竞拍出
   ///价,但是仍然需要抵押保证金。同一个地址可以放置多个竞拍。
    function bid(bytes32 _blindedBid)
        onlyBefore(biddingEnd)
    {
        bids[msg.sender].push(Bid({
            blindedBid: _blindedBid,
            deposit: msg.value
        }));
    }

   ///揭开你的盲拍竞价。你将会拿回除了最高出价外的所有竞拍保证金
   ///以及正常的无效盲拍保证金。
    function reveal(uint[] _values, bool[] _fake,
                    bytes32[] _secret)
        onlyAfter(biddingEnd)
        onlyBefore(revealEnd)
    {
        uint length = bids[msg.sender].length;
        if (_values.length != length || _fake.length != length ||
                    _secret.length != length)
            throw;
        uint refund;
        for (uint i = 0; i < length; i++)
        {
            var bid = bids[msg.sender][i];
            var (value, fake, secret) =
                    (_values[i], _fake[i], _secret[i]);
            if (bid.blindedBid != sha3(value, fake, secret))
                //出价未被正常揭拍,不能取回保证金。
                continue;
            refund += bid.deposit;
            if (!fake && bid.deposit >= value)
                if (placeBid(msg.sender, value))
                    refund -= value;
            //保证发送者绝不可能重复取回保证金
            bid.blindedBid = 0;
        }
        msg.sender.send(refund);
    }

    //这是一个内部 (internal)函数,
   //意味着仅仅只有合约(或者从其继承的合约)可以调用
    function placeBid(address bidder, uint value) internal
            returns (bool success)
    {
        if (value <= highestBid)
            return false;
        if (highestBidder != 0)
            //退还前一个最高竞拍出价
            highestBidder.send(highestBid);
        highestBid = value;
        highestBidder = bidder;
        return true;
    }

   ///竞拍结束后发送最高出价到竞拍人
    function auctionEnd()
        onlyAfter(revealEnd)
    {
        if (ended) throw;
        AuctionEnded(highestBidder, highestBid);
        //发送合约拥有所有的钱,因为有一些保证金退回可能失败了。
        beneficiary.send(this.balance);
        ended = true;
    }

    function () { throw; }
}
Safe Remote Purchase 安全的远程购物

contract Purchase
{
    uint public value;
    address public seller;
    address public buyer;
    enum State { Created, Locked, Inactive }
    State public state;
    function Purchase()
    {
        seller = msg.sender;
        value = msg.value / 2;
        if (2 * value != msg.value) throw;
    }
    modifier require(bool _condition)
    {
        if (!_condition) throw;
        _
    }
    modifier onlyBuyer()
    {
        if (msg.sender != buyer) throw;
        _
    }
    modifier onlySeller()
    {
        if (msg.sender != seller) throw;
        _
    }
    modifier inState(State _state)
    {
        if (state != _state) throw;
        _
    }
    event aborted();
    event purchaseConfirmed();
    event itemReceived();

   ///终止购物并收回以太。仅仅可以在合约未锁定时被卖家调用。
    function abort()
        onlySeller
        inState(State.Created)
    {
        aborted();
        seller.send(this.balance);
        state = State.Inactive;
    }

   ///买家确认购买。交易包含两倍价值的(`2 * value`)以太。
   ///这些以太会一直锁定到收货确认(confirmReceived)被调用。
    function confirmPurchase()
        inState(State.Created)
        require(msg.value == 2 * value)
    {
        purchaseConfirmed();
        buyer = msg.sender;
        state = State.Locked;
    }

    ///确认你(买家)收到了货物,这将释放锁定的以太。
    function confirmReceived()
        onlyBuyer
        inState(State.Locked)
    {
        itemReceived();
        buyer.send(value);//我们有意忽略了返回值。
        seller.send(this.balance);
        state = State.Inactive;
    }
    function() { throw; }
}

小额支付通道

待补

转自:
https://github.com/twq0076262/solidity-zh/blob/master/solidity-example.md

如果你希望高效的学习以太坊DApp开发,可以访问汇智网提供的最热门在线互动教程:

其他更多内容也可以访问这个以太坊博客

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Solidity编程开发实例 的相关文章

  • 使用CUDA和CUFFT进行快速1D卷积的示例

    使用CUDA和CUFFT进行快速1D卷积的示例 在计算机视觉 数字信号处理和机器学习中 卷积是一种常见的操作 然而 卷积操作通常需要大量计算 因此需要一种高效的方法来完成 CUDA和CUFFT可以用于对使用FFT的快速1D卷积进行加速 在本
  • KMP比较简单的讲法。

    转载链接 http blog csdn net yearn520 article details 6729426 我们在一个母字符串中查找一个子字符串有很多方法 KMP是一种最常见的改进算法 它可以在匹配过程中失配的情况下 有效地多往后面跳
  • 各种汇编器masm masm32 fasm nasm yasm gas的区别

    原文地址 http www verydemo com demo c269 i661 html masm MASM是微软公司开发的汇编开发环境 拥有可视化的开发界面 使开发人员不必再使用DOS环境进行汇编的开发 编译速度快 支持80x86汇编
  • 基础的定义必须先于派生合约的定义

    我有两个不同的文件 即 Project1 sol 和 Project2 sol Project2 sol 就像 import Project1 sol contract Project2 address newProject1Address
  • Solidity 中的动态数组

    我想声明一个简单的数组 动态列表 一个set函数推入一个字符串和一个get函数返回动态数组中保存的所有字符串 我搜索了很多但找不到这个简单的东西 这是我的解决方案 你需要experimental ABIEncoderV2返回字符串数组 pr
  • Matlab中文注释在Linux中乱码解决

    Linux for Matlab中文注释乱码 Linux for Matlab中文注释乱码 亲测有效 中文注释乱码的原因是windows下的m文件采用的是gbk编码 只要将所有的m文件转成 utf8文件 显示就正常了 查看支持的语言 enc
  • python能用来做什么?这3大主要用途你一定要知道!(实用)_python能做什么

    导读 如果你想学Python 或者你刚开始学习Python 那么你可能会问 我能用Python做什么 这个问题不好回答 因为Python有很多用途 但是随着时间 我发现有Python主要有以下三大主要应用 Web开发 数据科学 包括机器学习
  • Go 语言中切片的使用和理解

    切片与数组类似 但更强大和灵活 与数组一样 切片也用于在单个变量中存储相同类型的多个值 然而 与数组不同的是 切片的长度可以根据需要增长和缩小 在 Go 中 有几种创建切片的方法 使用 datatype values 格式 从数组创建切片
  • Java18都在路上了,你还在用Java8吗?

    Java18都在路上了 你还在用Java8吗 在开始前我有一些资料 是我根据自己从业十年经验 熬夜搞了几个通宵 精心整理了一份 Java的资料从专业入门到高级教程 工具包 点个关注 全部无偿共享给大家 在评论区回复 888 之后私信回复 8
  • C 语言运算符详解

    C 语言中的运算符 运算符用于对变量和值进行操作 在下面的示例中 我们使用 运算符将两个值相加 int myNum 100 50 虽然 运算符通常用于将两个值相加 就像上面的示例一样 它还可以用于将变量和值相加 或者将变量和另一个变量相加
  • 如何将 uint256 变量转换为 int256 变量?

    我试图打印uint timeStamp通过输入return timeStamp 就在下面return price 从这段代码 pragma solidity 0 6 7 import chainlink contracts src v0 6
  • 未找到源“@openzeppelin/contracts/token/ERC721/ERC721.sol”:不支持文件导入回调

    我已使用 Solidity 扩展将 Open Zeppelin ERC721 令牌标准导入到我的 VS Code 中 但在我的所有 OZ 导入语句中看到以下警告 错误截图 https i stack imgur com cfVUH png
  • 为什么需要括号来访问 python 中的 Solidity 合约变量?

    在 Solidity 合约中 有如下声明 contract Lottery is VRFConsumerBase Ownable address payable public players 在同一合同的其他地方 有一项转让如下 recen
  • web3.js如何搜索所有曾经创建的合约并查看地址

    我是 web3 js 和 Solidity 的新手 我的问题与我们在区块链上搜索的方式有关 如果我们知道合约地址 就很容易搜索特定合约 然而 我们如何使用最初用于创建合约的地址来查找和识别特定类型的合约 例如 我有一个合同 Contract
  • Solidity:在公共方法中返回数组

    我正在尝试创建一个返回数组的公共函数 这是错误 返回参数类型映射 uint256 gt struct ItemList Item storage ref 不能隐式转换为预期类型 第一个的类型 返回变量 uint256 内存 pragma s
  • 如何在 Python 和 web3.py 中获取 Solidity 恢复/需要错误原因

    我正在努力抓住要求 交易恢复时出错 但我得到了交易的哈希值 我正在使用web3 py def addParticipants request web3 Web3 HTTPProvider settings Blockchain IP add
  • Solidity - 输入 JSON 描述的 Solidity 代码

    我想编译我的以太坊 HelloWorld sol 智能合约 在所有教程中 您都是这样做的 var solc require solc var compiledContract solc compile fs readFileSync Hel
  • 如何删除solidity中的数组项

    我正在尝试删除 Solidity 数组中的某个项目 我正在探索一些文章 https ethereum stackexchange com questions 1527 how to delete an element at a certai
  • 错误:数字最多只能安全存储 53 位

    我正在尝试在 quorum 中执行原始事务 但收到错误 错误 数字最多只能安全存储 53 位 代码是 web3 eth sendSignedTransaction rawTx then function transactionReciept
  • 如何从外部资源将库连接到智能合约?

    pragma solidity 0 4 15 import ERC20 sol import SafeMath sol 如何连接安全数学 sol来自外部 非本地的 资源 虽然詹姆斯的答案是有效的 但我不建议从在线存储库链接合同的依赖项 这是

随机推荐

  • pandoc -crossref插件实现markdwon文档转word后公式编号自定义

    pandoc crossref插件实现markdwon文档转word后公式编号自定义 借助markdown撰写论文还是有一些优势的 公式可以通过vscode 提示直接快速地写出来 图片按照链接插入以后就可以自动更新图源 论文提交的时候需要转
  • Aviator 常见使用

    学习使用AviatorScript 写脚本对数据进行处理 这边写一些常见的例子 都使用表达式的方式 使用文本的话 无法传具体的参数 aviator maven最新的引用
  • 基于stm32单片机汽车胎压温度检测Proteus仿真程序

    采用stm32单片机作为主控CPU 采用BMP180传感器来测量气压和温度 采用LCD1602显示气压和温度 并且通过串口打印框也可以显示当前的气压和温度 完美的模拟出汽车胎压和温度检测相关功能 程序采用keil5编写 并且有中文注释 新手
  • [XAMPP的安装及使用教程] BUG解决

    说明 XAMPP的安装及使用教程 https blog csdn net qq 36595013 article details 80373597 转载 本文是针对原博客连接如上 安装过程中出现的bug进行解决 BUG1 前提 mysql端
  • 基于深度学习的高精度课堂人脸检测系统(PyTorch+Pyside6+YOLOv5模型)

    摘要 基于深度学习的高精度课堂人脸检测系统可用于日常生活中或野外来检测与定位课堂人脸目标 利用深度学习算法可实现图片 视频 摄像头等方式的课堂人脸目标检测识别 另外支持结果可视化与图片或视频检测结果的导出 本系统采用YOLOv5目标检测模型
  • 1084. 销售分析III(SQL)

    题目 https leetcode cn com problems sales analysis iii Table Product Column Name Type product id int product name varchar
  • demo演示是什么意思_路演(融资演示)时要注意些什么?

    路演 融资演示 究竟重不重要 如果你的企业足够优秀 那可能路演对你来说就没那么重要 甚至都不需要路演 可能就有很多投资人抢着来投你 但能达到这个水平的毕竟是少数 更多的是默默无闻的创业者 如果你的企业还没有那么优秀 或者你的产品还不够成熟
  • Python_捕获未知错误代码

    try num int input 请输入一个整数 result 8 num print result except Exception as result print 未知错误 s result
  • VScode编译调试C++环境

    首先去官网下载vscodehttps code visualstudio com 为了编译C C 要使用gcc Windows本身不支持gcc 所以有了MinGW 我用的是dev带的MinGW 也可以自己安装MinGW 或者用VS的编译器
  • VTM7.0配置并运行(windows系统)

    文章目录 一 下载安装VTM 下载方式一 下载方式二 1 解压VTM软件压缩包 2 在解压好的目录里新建 build 文件夹 二 下载安装Cmake 1 下载Cmake并解压 2 配置Cmake环境变量 三 编译 方法一 界面 1 打开 c
  • Netty案例(二)之耗时任务的处理

    文章目录 netty版本 Netty耗时任务的处理 代码案例 Handler 自定义业务线程池 Context中添加线程池 netty版本 使用的netty版本是io netty netty all 4 1 33 Final Netty耗时
  • 全网最好的免费开源ERP:Odoo库存路线规则设置应用详解

    引言 在库存管理中 供应链战略确定了产品何时应该采购或制造 交付到分销中心 并最终提供给零售渠道 在开源智造 Odoo免费开源ERP解决方案中 可以使用WMS应用中的仓库路线来配置产品的供应链策略 其中包括库内作业的拉取和推送规则 一旦一切
  • Java架构直通车——深入理解B+树

    文章目录 引入 AVL树和B树 AVL树 红黑树 B树 B 树 数据库为什么不使用二叉树 为什么使用B 树 与B树的区别 引入 AVL树和B树 AVL树 平衡二叉搜索树是基于二分法的策略提高数据的查找速度的二叉树的数据结构 平衡二叉搜索树的
  • 删除双系统中的一个linux,双系统删除linux

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 双系统删除linux系统2008 10 12 00 27方法一 我们知道在xp里一个很好的分区工具 PQ partition magic 但在双系统下 由于linux改变了xp里C盘的一个启动项
  • LVGL-obj对象

    对象创建 1 lv中所有的对象都以obj为基础进行扩展的 创建对象时父对象是空 则在显示器屏链表增加一个节点 父对象非空则在其子链表增加一个节点 创建完成会通过parent gt signal cb parent LV SIGNAL CHI
  • MySQL数据库之索引

    目录 前言 一 索引的概念 二 索引的作用和副作用 1 索引的作用 2 索引的副作用 3 创建索引的原则依据 总结 三 索引的分类和创建 1 普通索引 1 直接创建索引 2 修改表方式创建 3 创建表的时候指定索引 不建议 2 唯一索引 1
  • 国标GB28181安防视频平台EasyGBS显示状态正常,却无法播放该如何解决?

    国标GB28181视频平台EasyGBS是基于国标GB T28181协议的行业内安防视频流媒体能力平台 可实现的视频功能包括 实时监控直播 录像 检索与回看 语音对讲 云存储 告警 平台级联等功能 国标GB28181视频监控平台部署简单 可
  • C/C++函数参数读取顺序

    说到C C 函数参数读取顺序 很多人都知道在入栈时是从右至左的 可是真的有那么简单吗 先看一个例子 include
  • 允许chatgpt上传文件的插件

    https chrome google com webstore detail chatgpt file uploader ext becfinhbfclcgokjlobojlnldbfillpf related 事实上在其它浏览器 例如e
  • Solidity编程开发实例

    Solidity 编程开发实例 Voting 投票 接下来的智能合约教程非常复杂 但展示了很多Solidity的特性 它实现了一个入门的投票合约 当然 电子选举的主要问题是如何赋予投票权给准确的人 并防止操纵 我们不能解决所有的问题 但至少