Skip to content

Latest commit

 

History

History
484 lines (362 loc) · 17.6 KB

k1one_WICL1st.md

File metadata and controls

484 lines (362 loc) · 17.6 KB

Web3 URL 残酷共学第 1 期残酷指引

⚠️ 正式开始前请确保你在身体上和精神上都处于合适的状态,请刻意练习,残酷面对 🆒。为方便检索 The First Web3 URL Intensive CoLearning 简写为 WICL1st,第 2 期即为WICL2nd,第 3 期即为 WICL3rd,以此类推。

⚠️ 报名需要按要求认真填写下面 [ XXX ] 部分,方可通过报名审核,通过审核即可开始自主学习。


[ 你的名字 ]

  1. 自我介绍:

    我是k1one,在读大二学生,目前熟悉solidity,希望能在学习中提升自己,找到自己的idea

  2. 组队期待:

    希望认识志同道合的小伙伴,找到奇妙的idea

  3. 你认为你会完成本次 Web3 URL 的残酷学习吗?

    Yes 95%


第 1 期共学时间计划

  • 7 月 8 日 - 7 月 14 日

    • 自我介绍:大家按要求更新上方自我介绍,方面大家互相了解,及后续自由组队方向。

    • Web3 URL 残酷共学频道报道:大家可以自由在残酷共学群里交流分享,互动答疑,根据自身学习阶段情况随时开启自由组队。

    • 课前学习:了解残酷共学流程,GitHub 协作共学基础;Web3:// 协议课前学习。

  • 7 月 15 日 - 7 月 21 日

    • 7 月 15 日 周一晚 8 点- 9 点(北京时间): 第 1 次公开课分享
    • 本周共学内容: 涉及 Web3:// 的背景和演进历史;支持 Web3:// 协议的访问方式 (gateway 和 EVM browser)来浏览以太坊上面的数据;熟悉使用 Web3:// 和 EthStorage 早期测试网来部署简单的去中心化网站。
    • Homework1:课程 PPT
  • 7 月 22 日 - 7 月 28 日

    • 7 月 22 日 周一晚 8 点- 9 点(北京时间): 第 2 次公开课分享

    • 本周共学内容: 涉及 Web3:// 高级开发工具,包括:在命令行通过 web3curl 来通过 Web3:// 协议下载数据,通过 ethfs-uploader 批量上传网页数据,通过 manual 模式来搭建去中心化多人交互全链网站;及深入理解以太坊的存储模型和 gas 开销等。

    • 边学边用实战开发: 根据组队情况自由安排。

    • Homework2:课程 PPT

  • 7 月 29 日 - 8 月 4 日

    • 7 月 29 日 周一晚 8 点- 9 点(北京时间): 第 3 次公开课分享
    • 本周共学内容: 涉及实际应用案例分享及未来以太坊基础设施在 Web3:// 的重要作用及开发方向等。
    • 边学边用实战开发: 根据组队情况自由安排。
    • 结营分享: 具体时间及详情另在「Web3 URL 残酷共学频道」通知。

笔记证明 Notes Proof

07.15

  • 今日学习时间:1h
  • 学习内容小结:学习web3 url 和 基础的业务场景
  • Homework 部分(如果有安排需要填写证明完成)
  • Question and Ideas(有什么疑问/或者想法,可以记在这里,也可以分享到共学频道群讨论交流)

07.16

学习时间:1h

学习内容小结:学习solidity的分账

分账合约(PaymentSplit)具有以下几个特点:

  1. 在创建合约时定好分账受益人payees和每人的份额shares
  2. 份额可以是相等,也可以是其他任意比例。
  3. 在该合约收到的所有ETH中,每个受益人将能够提取与其分配的份额成比例的金额。
  4. 分账合约遵循Pull Payment模式,付款不会自动转入账户,而是保存在此合约中。受益人通过调用release()函数触发实际转账。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

/**
 * 分账合约 
 * @dev 这个合约会把收到的ETH按事先定好的份额分给几个账户。收到ETH会存在分账合约中,需要每个受益人调用release()函数来领取。
 */
contract PaymentSplit{

分账合约中共有3个事件:

  • PayeeAdded:增加受益人事件。

  • PaymentReleased:受益人提款事件。

  • PaymentReceived:分账合约收款事件。

        // 事件
        event PayeeAdded(address account, uint256 shares); // 增加受益人事件
        event PaymentReleased(address to, uint256 amount); // 受益人提款事件
        event PaymentReceived(address from, uint256 amount); // 合约收款事件
    

    分账合约中共有5个状态变量,用来记录受益地址、份额、支付出去的ETH等变量:

    • totalShares:总份额,为shares的和。
    • totalReleased:从分账合约向受益人支付出去的ETH,为released的和。
    • payeesaddress数组,记录受益人地址
    • sharesaddressuint256的映射,记录每个受益人的份额。
    • releasedaddressuint256的映射,记录分账合约支付给每个受益人的金额。
        uint256 public totalShares; // 总份额
        uint256 public totalReleased; // 总支付
    
        mapping(address => uint256) public shares; // 每个受益人的份额
        mapping(address => uint256) public released; // 支付给每个受益人的金额
        address[] public payees; // 受益人数组
    

分账合约中共有6个函数:

  • 构造函数:始化受益人数组_payees和分账份额数组_shares,其中数组长度不能为0,两个数组长度要相等。_shares中元素要大于0,_payees中地址不能为0地址且不能有重复地址。
  • receive():回调函数,在分账合约收到ETH时释放PaymentReceived事件。
  • release():分账函数,为有效受益人地址_account分配相应的ETH。任何人都可以触发这个函数,但ETH会转给受益人地址account。调用了releasable()函数。
  • releasable():计算一个受益人地址应领取的ETH。调用了pendingPayment()函数。
  • pendingPayment():根据受益人地址_account, 分账合约总收入_totalReceived和该地址已领取的钱_alreadyReleased,计算该受益人现在应分的ETH
  • _addPayee():新增受益人函数及其份额函数。在合约初始化的时候被调用,之后不能修改。
    /**
     * @dev 初始化受益人数组_payees和分账份额数组_shares
     * 数组长度不能为0,两个数组长度要相等。_shares中元素要大于0,_payees中地址不能为0地址且不能有重复地址
     */
    constructor(address[] memory _payees, uint256[] memory _shares) payable {
        // 检查_payees和_shares数组长度相同,且不为0
        require(_payees.length == _shares.length, "PaymentSplitter: payees and shares length mismatch");
        require(_payees.length > 0, "PaymentSplitter: no payees");
        // 调用_addPayee,更新受益人地址payees、受益人份额shares和总份额totalShares
        for (uint256 i = 0; i < _payees.length; i++) {
            _addPayee(_payees[i], _shares[i]);
        }
    }

    /**
     * @dev 回调函数,收到ETH释放PaymentReceived事件
     */
    receive() external payable virtual {
        emit PaymentReceived(msg.sender, msg.value);
    }

    /**
     * @dev 为有效受益人地址_account分帐,相应的ETH直接发送到受益人地址。任何人都可以触发这个函数,但钱会打给account地址。
     * 调用了releasable()函数。
     */
    function release(address payable _account) public virtual {
        // account必须是有效受益人
        require(shares[_account] > 0, "PaymentSplitter: account has no shares");
        // 计算account应得的eth
        uint256 payment = releasable(_account);
        // 应得的eth不能为0
        require(payment != 0, "PaymentSplitter: account is not due payment");
        // 更新总支付totalReleased和支付给每个受益人的金额released
        totalReleased += payment;
        released[_account] += payment;
        // 转账
        _account.transfer(payment);
        emit PaymentReleased(_account, payment);
    }

    /**
     * @dev 计算一个账户能够领取的eth。
     * 调用了pendingPayment()函数。
     */
    function releasable(address _account) public view returns (uint256) {
        // 计算分账合约总收入totalReceived
        uint256 totalReceived = address(this).balance + totalReleased;
        // 调用_pendingPayment计算account应得的ETH
        return pendingPayment(_account, totalReceived, released[_account]);
    }

    /**
     * @dev 根据受益人地址`_account`, 分账合约总收入`_totalReceived`和该地址已领取的钱`_alreadyReleased`,计算该受益人现在应分的`ETH`。
     */
    function pendingPayment(
        address _account,
        uint256 _totalReceived,
        uint256 _alreadyReleased
    ) public view returns (uint256) {
        // account应得的ETH = 总应得ETH - 已领到的ETH
        return (_totalReceived * shares[_account]) / totalShares - _alreadyReleased;
    }

    /**
     * @dev 新增受益人_account以及对应的份额_accountShares。只能在构造器中被调用,不能修改。
     */
    function _addPayee(address _account, uint256 _accountShares) private {
        // 检查_account不为0地址
        require(_account != address(0), "PaymentSplitter: account is the zero address");
        // 检查_accountShares不为0
        require(_accountShares > 0, "PaymentSplitter: shares are 0");
        // 检查_account不重复
        require(shares[_account] == 0, "PaymentSplitter: account already has shares");
        // 更新payees,shares和totalShares
        payees.push(_account);
        shares[_account] = _accountShares;
        totalShares += _accountShares;
        // 释放增加受益人事件
        emit PayeeAdded(_account, _accountShares);
    }

学习完分账后,对分账合约尝试进行具体实现。

然后学习了代币的线性释放合约:

线性释放合约中共有3个函数。

  • 构造函数:初始化受益人地址,归属期(秒), 起始时间戳。参数为受益人地址beneficiaryAddress和归属期durationSeconds。为了方便,起始时间戳用的部署时的区块链时间戳block.timestamp

  • release():提取代币函数,将已释放的代币转账给受益人。调用了vestedAmount()函数计算可提取的代币数量,释放ERC20Released事件,然后将代币transfer给受益人。参数为代币地址token

  • vestedAmount():根据线性释放公式,查询已经释放的代币数量。开发者可以通过修改这个函数,自定义释放方式。参数为代币地址token和查询的时间戳timestamp

        /**
         * @dev 初始化受益人地址,释放周期(秒), 起始时间戳(当前区块链时间戳)
         */
        constructor(
            address beneficiaryAddress,
            uint256 durationSeconds
        ) {
            require(beneficiaryAddress != address(0), "VestingWallet: beneficiary is zero address");
            beneficiary = beneficiaryAddress;
            start = block.timestamp;
            duration = durationSeconds;
        }
    
        /**
         * @dev 受益人提取已释放的代币。
         * 调用vestedAmount()函数计算可提取的代币数量,然后transfer给受益人。
         * 释放 {ERC20Released} 事件.
         */
        function release(address token) public {
            // 调用vestedAmount()函数计算可提取的代币数量
            uint256 releasable = vestedAmount(token, uint256(block.timestamp)) - erc20Released[token];
            // 更新已释放代币数量   
            erc20Released[token] += releasable; 
            // 转代币给受益人
            emit ERC20Released(token, releasable);
            IERC20(token).transfer(beneficiary, releasable);
        }
    
        /**
         * @dev 根据线性释放公式,计算已经释放的数量。开发者可以通过修改这个函数,自定义释放方式。
         * @param token: 代币地址
         * @param timestamp: 查询的时间戳
         */
        function vestedAmount(address token, uint256 timestamp) public view returns (uint256) {
            // 合约里总共收到了多少代币(当前余额 + 已经提取)
            uint256 totalAllocation = IERC20(token).balanceOf(address(this)) + erc20Released[token];
            // 根据线性释放公式,计算已经释放的数量
            if (timestamp < start) {
                return 0;
            } else if (timestamp > start + duration) {
                return totalAllocation;
            } else {
                return (totalAllocation * (timestamp - start)) / duration;
            }
        }
    

07.17

学习时长:2h

学习内容小结:学习chainlink中Autumation有关知识并编写实例,进行性具体部署与测试

实例要求:

  • 试想一个小游戏,数组 health 用于存储 10 个角色的 HP(healthPoint)
  • HP 初始值为 1000,每次攻击(fight)会降低 100。
  • 同时满足以下两个条件,角色就可以通过 Automation 补充为 1000:
    1. 如果生命值不足 1000
    1. 经过某个时间间隔 interval

实现代码:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.7;

import "@chainlink/contracts/src/v0.8/automation/AutomationCompatible.sol";

contract AutomationTask is AutomationCompatible {
    
    uint256 public constant SIZE = 10;
    uint256 public constant MAXIMUM_HEALTH = 1000;
    uint256[SIZE] public healthPoint;
    uint256 public lastTimeStamp;
    uint256 public constant interval = 3600; // 1 hour in seconds

    /*
     * 步骤 1 - 在构造函数中完成数组 healthPoint 的初始化
     */    
    constructor() {
        lastTimeStamp = block.timestamp;
        
        // 初始化 healthPoint 数组,每个元素的初始值为 MAXIMUM_HEALTH
        for (uint256 i = 0; i < SIZE; i++) {
            healthPoint[i] = MAXIMUM_HEALTH;
        }
    }

    /*
     * 步骤 2 - 定义 fight 函数
     * 使得用户可以通过 fight 函数改变数组中的生命值
     * fight 函数接收一个参数 fighter,代表数组中的下标
     */
    function fight(uint256 fighter) public {
        require(fighter < SIZE, "Invalid fighter index");
        if (healthPoint[fighter] >= 100) {
            healthPoint[fighter] -= 100;
        } else {
            healthPoint[fighter] = 0;
        }
    }

    /* 
     * 步骤 3 - 通过 checkUpKeep 来检测:
     * 1. 数组 healthPoint 中的数值是否小于 1000
     * 2. 是否经过了时间间隔 interval
     * 
     * 注意:
     * 这部分操作将由 Chainlink 预言机节点在链下计算,本地环境中已由脚本配置
     * 可以尝试在 checkUpKeep 函数中改变状态,观察是否会发生改变
     */      
    function checkUpkeep(
        bytes memory /* checkData */ 
    ) 
        public 
        view 
        override 
        returns (
            bool upkeepNeeded,
            bytes memory /* performData */
        )
    {
        bool isHealthLow = false;
        for (uint256 i = 0; i < SIZE; i++) {
            if (healthPoint[i] < MAXIMUM_HEALTH) {
                isHealthLow = true;
                break;
            }
        }
        bool isIntervalPassed = (block.timestamp - lastTimeStamp) >= interval;
        upkeepNeeded = isHealthLow && isIntervalPassed;
    }

    /* 
     * 步骤 4 - 通过 performUpkeep 来完成将补足数组中生命值的操作
     * 例如发现 healthPoint[0] = 500,则将其增加 500 变为 1000
     * 
     * 注意:
     * 可以通过 performData 使用 checkUpkeep 的运算结果,减少 gas 费用
     */
    function performUpkeep(
        bytes memory /* performData */
    ) 
        external 
        override 
    {
        bool isHealthLow = false;
        for (uint256 i = 0; i < SIZE; i++) {
            if (healthPoint[i] < MAXIMUM_HEALTH) {
                healthPoint[i] = MAXIMUM_HEALTH;
                isHealthLow = true;
            }
        }
        if (isHealthLow) {
            lastTimeStamp = block.timestamp;
        }
    }
}

小结:通过对Automation的学习,初步了解了chainlink的Autoation合约的用法和具体实现

07.18

学习时长:0.5h

学习内容小结:学习Web3URL部分的知识,了解相关原理与分析相关合约源码内容,大概了解了合约的实现原理和相关函数的实现

07.19

07.20

学习时长:1h

学习内容小结:Web3URL协议的框架结构和Web3:// CLIENTS 学习了解

07.21

学习时长:2h

学习内容小结:学习和熟悉了WEB3URL的使用

Homework 部分:

1.查询0x42069abfe407c60cf4ae4112bedead391dba1cdb 1312

web3://0x42069abfe407c60cf4ae4112bedead391dba1cdb/ownerOf/1312?returns=(address)

查询结果:"0x3e720f62DB92cfc9f486c9D739E7753fEAc83Faa"

2.查询地址为0xDc2BFea46CD671699013B5230850c1DB034690b0的USDT余额

web3://0xdac17f958d2ee523a2206206994597c13d831ec7/balanceOf/0xDc2BFea46CD671699013B5230850c1DB034690b0?returns=(uint256)

查询结果:"0x0"

3.auto-model:

合约地址:0x72e52647818Bd2F49A0CbB51255317209617eBd1

web3://0x72e52647818Bd2F49A0CbB51255317209617eBd1:11155111/sayHello

结果:Hello World

4.manual model:

合约地址:0x299C58F990e832D6748CB0Aea0A9c71118c8700D

web3://0x299C58F990e832D6748CB0Aea0A9c71118c8700D:11155111

结果:Hello World

07.22

学习时长:1h

学习内容小结:第二次公开课学习

07.25

学习时长:1.5h

学习内容系小结:尝试用auto model模型进行项目编写

07.26

学习时长:1h

学习内容小结:了解并学习EthStorage相关知识

07.27

学习时长:2h

学习内容小结:尝试学习ethfs-cli工具的使用