基于EOS的区块链捐赠平台合约发开

最近在做一个简单的捐赠平台的DAPP,做一下笔记。DAPP的需求如下:

  1. 用户可捐赠指定数量的ZJUBCA(协会token的symbol)和EOS数量。先存入denote合约(本合约)账户,在满足一定数量后自动转入协会基金账户。
  2. 显示当前已收到捐赠的ZJUBCA和EOS总数。
  3. 显示当前用户已捐赠的ZJUBCA数量和EOS数量。
  4. 捐赠排行榜。对已进行捐赠的用户按捐赠数量进行排名。

基本思路

合约需要做的事比较简单,现在对上图做简单的解释。

  1. 用户可以捐赠ZJUBCA和EOS给donate合约,将捐赠不同的token会触发不同的合约内action(donatezjubca和donateeos),触发action之后,合约会更新区块链上相应的表(multi_index)并且向合约进行一次转账。
  2. 合约定期检查自己账户内ZJUBCA和EOS数量是否达到一定数量,将其转出到协会基金会中(ZJUBCA),并且更新区块链中的表。

代码详解

zjubca.donate.hpp

#pragma once

//包含需要的头文件
#include <eosiolib/eosio.hpp>
#include <eosiolib/asset.hpp>
#include <eosiolib/symbol.hpp>
#include <string>

//宏定义 MAX_ZJUBCA_QUANTITY用于检查合约账户中ZJUBCA数量是否超过10000.0000 ZJUBCA
//MAX_EOS_QUANTITY 用于检查合约账户中EOS数量是否超过1000.000 EOS  (此处ZJUBCA精度是4位,EOS是三位)
#define MAX_ZJUBCA_QUANTITY 100000000
#define MAX_EOS_QUANTITY 1000000

using namespace eosio;
using namespace std;

namespace eosiosystem {
class system_contract;
}

//定义zjubca.donate合约这个类
class [[eosio::contract("zjubca.donate")]] Donate : public contract
{
private:
    //定义donator表的结构,该表用于存储捐赠者的基本信息
    //主键是donator_name,类型是name,ZJUBCA_amount和EOS_amount用于保存该用户总共捐赠的ZJUBCA和EOS数量,类型是assset
    // @abi table doantor
    struct [[eosio::table]] donator {
        name donator_name;
        asset ZJUBCA_amount;
        asset EOS_amount;

        auto primary_key() const {return donator_name.value;}  //声明主键是donator_name.value
    };

    //定义recipient表的结构,该表用于存储该合约的基本信息
    //主键是recipient_name,类型是name,(其实这个数据只有本合约)ZJUBCA_amount和EOS_amount用于保存本合约总共收到捐赠的ZJUBCA和EOS数量,类型是assset
    // @abi table recipient
    struct [[eosio::table]] recipient {
        name recipient_name;
        asset ZJUBCA_amount;
        asset EOS_amount;

        auto primary_key() const {return recipient_name.value;}  声明主键是recipient_name.value
    };

    //定义foundation表的结构,该表用于存储基金会的基本信息,和recipient类似
    // @abi table foundation
    struct [[eosio::table]] foundation {
        name foundation_name;
        asset ZJUBCA_amount;
        asset EOS_amount;

        auto primary_key() const {return foundation_name.value;}
    };
    

    //mutil_index<"donators"_n, donator>是mutil_index的初始化, (_donators是一个类)现在我们可以拿着_donators去实例化一张表
    typedef multi_index<"donators"_n, donator> _donators;
    typedef multi_index<"recipients"_n, recipient> _recipients;
    typedef multi_index<"foundation"_n, foundation> _foundation;

    _donators donator;      //实例化donator这张表
    _recipients recipient;     //实例化recipent这张表
    _foundation foundation;      //实例化foundation这张表

public:
    using contract::contract;

    //构造函数以及三张表的初始化
    Donate(name receiver, name code, datastream<const char*> ds):contract(receiver, code, ds), 
        donator(receiver, code.value), recipient(receiver, code.value), foundation(receiver, code.value){}

    //声明各个action
    [[eosio::action]] void start();     //合约开始的时候要执行start, 向recipient和foundation这两张表中插入数据
    [[eosio::action]] void end();       //删除表,需要时可以执行
    [[eosio::action]] void donatezjubca(name from, name to, asset quantity, string memo);    //用户捐赠ZJUBCA给合约
    [[eosio::action]] void donateeos(name from, name to, asset quantity, string memo);       //用户捐赠EOS给合约
    [[eosio::action]] void sendtofound(name from, name to, string memo);     //检查合约账户中EOS数量是否超过1000.000 EOS,以及ZJUBCA数量是否超过10000.0000 ZJUBCA,然后将其转到基金会
};

因为合约中捐赠操作涉及到token的转账,转账就涉及到合约的安全性问题,在现在的合约中,已经不使用eosio.code这个授权的方式进行转账了,大部分合约都是使用通知(require_recipient)的方式来进行转账。

具体操作如下:

  • 用户直接向合约账户转账,memo用于传递指定参数,如下所示,chen这个用户通过transfer操作向合约地址转了100.000 EOS,并且memo参数为donateeos。
cleos push action eosio.token transfer '["chen", "donate", "100.000 EOS", "donateeos"]' -p chen@active
  • eosio.token的transfer中包含以下两行代码,其中require_recipient(to)就是用来通知合约,也是合约的入口,会进入到合约代码中的apply函数中,apply函数见下面的代码。
require_recipient(from);
require_recipient(to);
  • 进入到合约 的apply函数中后,通过判断即可以进入相应的action。

画了一张过程图,如下所示:

zjubca.donate.cpp

#include "zjubca.donate.hpp"

void Donate::start()
{
    //确保只有本合约才有权力执行start这个action
    require_auth(_self);

    name recipient_account = name("donate");
    name foundation_account = name("zjubca");

    //查找recipient表中是否已经存在donate这个字段
    auto existing1 = recipient.find(recipient_account.value);
    //查找foundation表中是否已经存在zjubca这个字段
    auto existing2 = foundation.find(foundation_account.value);
    if (existing1 == recipient.end())
    {
        //如果没找到,插入初始化数据
        recipient.emplace(_self, [&](auto &new_recipient) {
            new_recipient.recipient_name = recipient_account;
            new_recipient.ZJUBCA_amount = asset(0, symbol("ZJUBCA", 4));
            new_recipient.EOS_amount = asset(0, symbol("EOS", 3));
        });
    }
    if (existing2 == foundation.end())
    {
        //如果没有找到,插入初始化数据
        foundation.emplace(_self, [&](auto &new_foundation) {
            new_foundation.foundation_name = foundation_account;
            new_foundation.ZJUBCA_amount = asset(0, symbol("ZJUBCA", 4));
            new_foundation.EOS_amount = asset(0, symbol("EOS", 3));
        });
    }
}

void Donate::donatezjubca(name from, name to, asset quantity, string memo)
{
    //建议先看apply函数(代码最后)中的注释代码

    //检查memo是否是"donatezjubca",防止其他转账操作而进入本action
    if (memo == "donatezjubca")
    {
        //进行一些assert操作
        eosio_assert(from != to, "cannot transfer to self");
        require_auth(from);
        //接收ZJUBCA的地址只能是本合约
        eosio_assert(to == _self, "only can donate to the contract");

        auto sym = quantity.symbol;
        eosio_assert(sym.is_valid(), "invalid symbol name");
        eosio_assert(sym == symbol("ZJUBCA", 4), "invalid symbol name");

        eosio_assert(quantity.is_valid(), "invalid quantity");
        eosio_assert(quantity.amount > 0, "must transfer positive quantity");

        eosio_assert(memo.size() <= 256, "memo has more than 256 bytes");

        //先从donators表中查询捐赠者
        auto existing1 = donator.find(from.value);
        if (existing1 == donator.end())
        {
            //不存在就在新增一个用户
            donator.emplace(_self, [&](auto &new_donator) {
                new_donator.donator_name = from;
                new_donator.ZJUBCA_amount = quantity;
                symbol sym = symbol("EOS", 3);
                auto zero = asset(0, sym);
                new_donator.EOS_amount = zero;
            });
        }
        else
        {
            //若已经存在,则修改用户数据
            donator.modify(existing1, same_payer, [&](auto &content) {
                content.ZJUBCA_amount += quantity;
            });
        }

        auto existing2 = recipient.find(to.value);
        if (existing2 != recipient.end())
        {
            //修改recipient表中数据
            recipient.modify(existing2, same_payer, [&](auto &content) {
                content.ZJUBCA_amount += quantity;
            });
        }
    }
}

void Donate::donateeos(name from, name to, asset quantity, string memo)
{
    //和doantezjubca逻辑类似
    if (memo == "donateeos")
    {
        eosio_assert(from != to, "cannot transfer to self");
        require_auth(from);
        eosio_assert(to == _self, "only can donate to the contract");

        auto sym = quantity.symbol;
        eosio_assert(sym.is_valid(), "invalid symbol name");
        eosio_assert(sym == symbol("EOS", 3), "invalid symbol name");

        eosio_assert(quantity.is_valid(), "invalid quantity");
        eosio_assert(quantity.amount > 0, "must transfer positive quantity");

        eosio_assert(memo.size() <= 256, "memo has more than 256 bytes");

        auto existing1 = donator.find(from.value);
        if (existing1 == donator.end())
        {
            donator.emplace(_self, [&](auto &new_donator) {
                new_donator.donator_name = from;
                new_donator.EOS_amount = quantity;
                symbol sym = symbol("ZJUBCA", 4);
                auto zero = asset(0, sym);
                new_donator.ZJUBCA_amount = zero;
            });
        }
        else
        {
            donator.modify(existing1, same_payer, [&](auto &content) {
                content.EOS_amount += quantity;
            });
        }

        auto existing2 = recipient.find(to.value);
        if (existing2 != recipient.end())
        {
            recipient.modify(existing2, same_payer, [&](auto &content) {
                content.EOS_amount += quantity;
            });
        }
    }
}

void Donate::end()
{
    //删除表操作
    auto donator_begin_it = donator.begin();
    while (donator_begin_it != donator.end())
    {
        donator_begin_it = donator.erase(donator_begin_it);
    }

    auto recipient_begin_it = recipient.begin();
    while (recipient_begin_it != recipient.end())
    {
        recipient_begin_it = recipient.erase(recipient_begin_it);
    }

    auto foundation_begin_it = foundation.begin();
    while (foundation_begin_it != foundation.end())
    {
        foundation_begin_it = foundation.erase(foundation_begin_it);
    }
}

void Donate::sendtofound(name from, name to, string memo)
{
    //assert
    eosio_assert(from != to, "cannot transfer to self");
    require_auth(from);
    eosio_assert(from == _self, "invalid transfer account");
    eosio_assert(to == name("zjubca"), "invalid transfer account");

    asset ZJUBCA_quantity = asset(MAX_ZJUBCA_QUANTITY, symbol("ZJUBCA", 4));
    asset EOS_quantity = asset(MAX_EOS_QUANTITY, symbol("EOS", 3));
    auto existing1 = recipient.find(from.value);
    if (existing1 != recipient.end())
    {
        const auto &st1 = *existing1;
        if (st1.ZJUBCA_amount >= ZJUBCA_quantity)
        {
            //查表并且检查合约地址中token数量是否发超过一定限额
            recipient.modify(st1, from, [&](auto &content) {
                content.ZJUBCA_amount -= ZJUBCA_quantity;
            });

            auto existing2 = foundation.find(to.value);
            if (existing2 != foundation.end())
            {
                const auto &st2 = *existing2;
                foundation.modify(st2, from, [&](auto &content) {
                    content.ZJUBCA_amount += ZJUBCA_quantity;
                });
            }
            action(
                permission_level{from, "active"_n},
                "zjubca.token"_n,
                "transfer"_n,
                std::make_tuple(from, to, ZJUBCA_quantity, memo))
                .send();
        }
        if (st1.EOS_amount >= EOS_quantity)
        {
            //逻辑同上
            recipient.modify(st1, from, [&](auto &content) {
                content.EOS_amount -= EOS_quantity;
            });

            auto existing2 = foundation.find(to.value);
            if (existing2 != foundation.end())
            {
                const auto &st2 = *existing2;
                foundation.modify(st2, from, [&](auto &content) {
                    content.EOS_amount += EOS_quantity;
                });
            }
            //此处由于是合约将自己的token进行转出,所以可以直接使用inline action操作,不存在安全性问题
            action(
                permission_level{from, "active"_n},
                "eosio.token"_n,
                "transfer"_n,
                std::make_tuple(from, to, EOS_quantity, memo))
                .send();
        }
    }
}

// EOSIO_DISPATCH(Donate, (start)(donatezjubca)(donateeos)(sendtofound)(end))
extern "C"
{
    //apply函数是所有EOS合约的入口
    //在EOS中,合约之间是可以相互调用的,假设A合约在B这个action中调用了C合约,那么receiver就是C合约,code就是A合约,action就是B这个action
    void apply(uint64_t receiver, uint64_t code, uint64_t action)
    {
        //判断是action的入口是本合约还是别的合约
        //如果调用方和被调用方式相同的,并且不是从transfer这个action进入的
        if (code == receiver && action != "transfer"_n.value)
        {
            switch (action)
            {
            //进入不同的action
            case "start"_n.value:
                execute_action(name(receiver), name(code), &Donate::start);    //使用execute_action进入相应的action
                break;
            case "end"_n.value:
                execute_action(name(receiver), name(code), &Donate::end);
                break;
            case "sendtofound"_n.value:
                execute_action(name(receiver), name(code), &Donate::sendtofound);
                break;
            default:
                break;
            }
        }
        //code == zjubca.token和code == eosio.token意为调用本合约的合约为zjubca.token和eosio.token,并且是从transfer这个action进入的
        else if ((code == "zjubca.token"_n.value || code == "eosio.token"_n.value) && action == "transfer"_n.value)
        {
            switch (code)
            {
            //如果是zjubca.token进入本合约,进行donatezjubca这个action
            case "zjubca.token"_n.value:
                execute_action(name(receiver), name(receiver), &Donate::donatezjubca);
                break;
            //如果是eosio.token进入本合约,进行donateeos这个action
            case "eosio.token"_n.value:
                execute_action(name(receiver), name(receiver), &Donate::donateeos);
                break;
            default:
                break;
            }
        }
    }
};
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值