⚠️ 正式开始前请确保你在身体上和精神上都处于合适的状态,请刻意练习,残酷面对 🆒。为方便检索 The First Web3 URL Intensive CoLearning 简写为 WICL1st,第 2 期即为WICL2nd,第 3 期即为 WICL3rd,以此类推。
⚠️ 报名需要按要求认真填写下面 [ XXX ] 部分,方可通过报名审核,通过审核即可开始自主学习。
-
自我介绍:
我是k1one,在读大二学生,目前熟悉solidity,希望能在学习中提升自己,找到自己的idea
-
组队期待:
希望认识志同道合的小伙伴,找到奇妙的idea
-
你认为你会完成本次 Web3 URL 的残酷学习吗?
Yes 95%
-
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 残酷共学频道」通知。
- 今日学习时间:1h
- 学习内容小结:学习web3 url 和 基础的业务场景
- Homework 部分(如果有安排需要填写证明完成)
- Question and Ideas(有什么疑问/或者想法,可以记在这里,也可以分享到共学频道群讨论交流)
学习时间:1h
学习内容小结:学习solidity的分账
分账合约(PaymentSplit
)具有以下几个特点:
- 在创建合约时定好分账受益人
payees
和每人的份额shares
。 - 份额可以是相等,也可以是其他任意比例。
- 在该合约收到的所有
ETH
中,每个受益人将能够提取与其分配的份额成比例的金额。 - 分账合约遵循
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
的和。payees
:address
数组,记录受益人地址shares
:address
到uint256
的映射,记录每个受益人的份额。released
:address
到uint256
的映射,记录分账合约支付给每个受益人的金额。
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; } }
学习时长:2h
学习内容小结:学习chainlink中Autumation有关知识并编写实例,进行性具体部署与测试
实例要求:
- 试想一个小游戏,数组 health 用于存储 10 个角色的 HP(healthPoint)
- HP 初始值为 1000,每次攻击(fight)会降低 100。
-
- 同时满足以下两个条件,角色就可以通过 Automation 补充为 1000:
-
- 如果生命值不足 1000
-
- 经过某个时间间隔 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合约的用法和具体实现
学习时长:0.5h
学习内容小结:学习Web3URL部分的知识,了解相关原理与分析相关合约源码内容,大概了解了合约的实现原理和相关函数的实现
无
学习时长:1h
学习内容小结:Web3URL协议的框架结构和Web3:// CLIENTS 学习了解
学习时长: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
学习时长:1h
学习内容小结:第二次公开课学习
学习时长:1.5h
学习内容系小结:尝试用auto model模型进行项目编写
学习时长:1h
学习内容小结:了解并学习EthStorage相关知识
学习时长:2h
学习内容小结:尝试学习ethfs-cli工具的使用