本文深入分析了Friend.tech的智能合约FriendtechSharesV1,FriendtechSharesV1合约继承自OpenZeppelin的Ownable合约,该合约赋予了合约所有权管理功能,使合约所有者能够执行特定的特权操作,详细解释了合约中的关键变量、事件、以及函数,包括手续费设置、价格计算、以及股份买卖的具体实现逻辑,文章还分析了买卖股份的函数(buyShares、sellShares),说明了函数内部的逻辑和注意事项。
Friend.tech 智能合约解析
大家好!欢迎阅读本文,我们将仔细研究 Friend.tech 智能合约的内部运作。我们将不会花太多时间介绍 Friend.tech 是什么。
我们将在这里详细解读 Friend.tech 智能合约的工作原理。如果你对这个激动人心的平台感兴趣,你来对地方了。让我们开始吧!
Friend.tech 正在改变我们对社交网络的看法。它不仅仅是连接朋友,它做了一些不同的事情。它将你的联系转化为一种被称为“股份”或“钥匙”的有价值的东西。想象一下能够投资于朋友的社交网络或一群用户以展示可信度。这是一种不同类型的社交应用。
在这个系统中,X 上的每个用户都可以变成一个社交代币。你可以购买这些代币、将它们出售以获利,或者因为代表的用户声誉增加而保留它们。
但这还不是全部——股份提供对影响者的特别聊天房间、优质内容等的访问。
Friend.tech 属于区块链应用的 Socialfi 领域,使其成为去中心化世界中一个真正独特和激动人心的补充。
该合约的巨大热度(2023/10/03)
截至撰写本博客时,FriendtechSharesV1 合约中存储的 ETH 量约为 29,735 ETH,相当于约 4930万 USD。
与该合约交互的交易数量已达到 950 万。这一活动水平反映出对该平台的重大兴趣和参与度。
现在,让我们深入了解 Friend.tech 智能合约。
在这一部分,我们将深入探讨驱动 Friend.tech 的智能合约的内部工作原理。
FriendtechSharesV1 合约如同一个紧凑的强大系统,代码只有大约 90 行(不包括 Ownable 合约)。这个小合约包含 五个重要变量、一个事件和 十个函数。更令人印象深刻的是,这个简洁的代码库可以处理大量资产。
下面,我们提供 FriendtechSharesV1 合约的代码供你审查。查看代码将为你提供更好的理解,了解它如何实现 Friend.tech 平台的创新功能。
FriendtechShares.sol – Medium
pragma solidity>=0.8.2<0.9.0;
// TODO: 事件,最终定价模型,
contract FriendtechSharesV1 is Ownable {
address public protocolFeeDestination;
uint256 public protocolFeePercent;
uint256 public subjectFeePercent;
event Trade(address trader, address subject, bool isBuy, uint256 shareAmount, uint256 ethAmount, uint256 protocolEthAmount, uint256 subjectEthAmount, uint256 supply);
// Shares Subject => (Holder => Balance)
mapping(address => mapping(address => uint256)) public sharesBalance;
// Shares Subject => Supply
mapping(address => uint256) public sharesSupply;
function setFeeDestination(address _feeDestination) public onlyOwner {
protocolFeeDestination = _feeDestination;
}
function setProtocolFeePercent(uint256 _feePercent) public onlyOwner {
protocolFeePercent = _feePercent;
}
function setSubjectFeePercent(uint256 _feePercent) public onlyOwner {
subjectFeePercent = _feePercent;
}
function getPrice(uint256 supply, uint256 amount) public pure returns (uint256) {
uint256 sum1 = supply == 0 ? 0 : (supply - 1) * (supply) * (2 * (supply - 1) + 1) / 6;
uint256 sum2 = supply == 0 && amount == 1 ? 0 : (supply - 1 + amount) * (supply + amount) * (2 * (supply - 1 + amount) + 1) / 6;
uint256 summation = sum2 - sum1;
return summation * 1 ether / 16000;
}
function getBuyPrice(address sharesSubject, uint256 amount) public view returns (uint256) {
return getPrice(sharesSupply[sharesSubject], amount);
}
function getSellPrice(address sharesSubject, uint256 amount) public view returns (uint256) {
return getPrice(sharesSupply[sharesSubject] - amount, amount);
}
function getBuyPriceAfterFee(address sharesSubject, uint256 amount) public view returns (uint256) {
uint256 price = getBuyPrice(sharesSubject, amount);
uint256 protocolFee = price * protocolFeePercent / 1 ether;
uint256 subjectFee = price * subjectFeePercent / 1 ether;
return price + protocolFee + subjectFee;
}
function getSellPriceAfterFee(address sharesSubject, uint256 amount) public view returns (uint256) {
uint256 price = getSellPrice(sharesSubject, amount);
uint256 protocolFee = price * protocolFeePercent / 1 ether;
uint256 subjectFee = price * subjectFeePercent / 1 ether;
return price - protocolFee - subjectFee;
}
function buyShares(address sharesSubject, uint256 amount) public payable {
uint256 supply = sharesSupply[sharesSubject];
require(supply > 0 sharesSubject == msg.sender, "只有股份的主题可以购买第一股");
uint256 price = getPrice(supply, amount);
uint256 protocolFee = price * protocolFeePercent / 1 ether;
uint256 subjectFee = price * subjectFeePercent / 1 ether;
require(msg.value >= price + protocolFee + subjectFee, "支付不足");
sharesBalance[sharesSubject][msg.sender] = sharesBalance[sharesSubject][msg.sender] + amount;
sharesSupply[sharesSubject] = supply + amount;
emit Trade(msg.sender, sharesSubject, true, amount, price, protocolFee, subjectFee, supply + amount);
(bool success1, ) = protocolFeeDestination.call{value: protocolFee}("");
(bool success2, ) = sharesSubject.call{value: subjectFee}("");
require(success1 && success2, "无法发送资金");
}
function sellShares(address sharesSubject, uint256 amount) public payable {
uint256 supply = sharesSupply[sharesSubject];
require(supply > amount, "无法出售最后一股");
uint256 price = getPrice(supply - amount, amount);
uint256 protocolFee = price * protocolFeePercent / 1 ether;
uint256 subjectFee = price * subjectFeePercent / 1 ether;
require(sharesBalance[sharesSubject][msg.sender] >= amount, "股份不足");
sharesBalance[sharesSubject][msg.sender] = sharesBalance[sharesSubject][msg.sender] - amount;
sharesSupply[sharesSubject] = supply - amount;
emit Trade(msg.sender, sharesSubject, false, amount, price, protocolFee, subjectFee, supply - amount);
(bool success1, ) = msg.sender.call{value: price - protocolFee - subjectFee}("");
(bool success2, ) = protocolFeeDestination.call{value: protocolFee}("");
(bool success3, ) = sharesSubject.call{value: subjectFee}("");
require(success1 && success2 && success3, "无法发送资金");
}
}
FriendtechShares.sol 由 ❤ 通过 GitHub 托管
FriendtechSharesV1 合约代码
在上面的代码片段中,FriendtechSharesV1 合约继承自 Ownable 合约,这是 OpenZeppelin 提供的流行库之一。这种继承为合约提供了所有权管理功能,使合约所有者能够执行特定的特权操作。
进一步检查代码后,可以明显看出合约中的所有变量和函数的可见性被设置为 “public”。这种可见性设置允许外部和内部调用访问这些变量和函数。
在检查完上述代码后,让我们更深入地了解 FriendtechSharesV1 合约的关键组成部分。
FriendtechShares-variables.sol – Medium
contract FriendtechSharesV1 is Ownable {
address public protocolFeeDestination;
uint256 public protocolFeePercent;
uint256 public subjectFeePercent;
// (...SNIPPET...)
// Shares Subject => (Holder => Balance)
mapping(address => mapping(address => uint256)) public sharesBalance;
// Shares Subject => Supply
mapping(address => uint256) public sharesSupply;
// (...SNIPPET...)
}
FriendtechShares-variables.sol 通过 GitHub 托管
FriendtechSharesV1 合约中的关键变量
FriendtechSharesV1 合约包括五个变量(不包括从 Ownable 合约继承的 _owner 变量)。以下是变量列表:
protocolFeeDestination:
此地址变量存储接收协议费用的目标地址。
protocolFeePercent:
此变量表示协议费用的百分比。
subjectFeePercent:
此变量表示主题费用的百分比。
sharesBalance:
此映射将股份主题(地址)与各自的持有者及其股份余额关联起来。
sharesSupply:
此映射跟踪每个股份主题的股份供应。
这些变量,protocolFeeDestination、protocolFeePercent、subjectFeePercent、sharesBalance 和 sharesSupply,具有公共可见性,便于外部跟踪或查看它们的值。
每当股份被交易时,交易事件会被触发
FriendtechSharesV1 合约提供一个名为 Trade 的事件。每当通过 buyShares 和 sellShares 函数交易股份时,会触发此事件。
Trade 事件记录每次交易的多个详细信息,包括:
gist:af41d4de58e9cdde301e382b12740404 – Medium
contract FriendtechSharesV1 is Ownable {
address public protocolFeeDestination;
uint256 public protocolFeePercent;
uint256 public subjectFeePercent;
// (...SNIPPET...)
function setFeeDestination(address _feeDestination) public onlyOwner {
protocolFeeDestination = _feeDestination;
}
function setProtocolFeePercent(uint256 _feePercent) public onlyOwner {
protocolFeePercent = _feePercent;
}
function setSubjectFeePercent(uint256 _feePercent) public onlyOwner {
subjectFeePercent = _feePercent;
}
// (...SNIPPET...)
}
FriendtechShares-onlyOwner.sol 通过 GitHub 托管
只有合约所有者可以访问的函数列表。
在上面的代码片段中,我们列出了仅合约所有者可以访问的函数。这些函数赋予合约所有者配置与该平台有关的三项重要方面的能力:
此函数使合约所有者能够指定接收协议费用的目标地址
此函数赋予合约所有者设置协议费用百分比的权限
此函数提供合约所有者设定主题费用百分比的能力
🚨值得注意的是,这三个函数没有输入验证、最大费用限制或时间锁机制。因此,合约所有者可以根据个人偏好设置这些值。
FriendtechShares-getPrice.sol – Medium
contract FriendtechSharesV1 is Ownable {
// (...SNIPPET...)
function getPrice(uint256 supply, uint256 amount) public pure returns (uint256) {
uint256 sum1 = supply == 0 ? 0 : (supply - 1) * (supply) * (2 * (supply - 1) + 1) / 6;
uint256 sum2 = supply == 0 && amount == 1 ? 0 : (supply - 1 + amount) * (supply + amount) * (2 * (supply - 1 + amount) + 1) / 6;
uint256 summation = sum2 - sum1;
return summation * 1 ether / 16000;
}
function getBuyPrice(address sharesSubject, uint256 amount) public view returns (uint256) {
return getPrice(sharesSupply[sharesSubject], amount);
}
function getSellPrice(address sharesSubject, uint256 amount) public view returns (uint256) {
return getPrice(sharesSupply[sharesSubject] - amount, amount);
}
// (...SNIPPET...)
}
FriendtechShares-getPrice.sol 通过 GitHub 托管
接下来,上述函数共同确定股份在 FriendtechSharesV1 合约内的买入和卖出价格。
这是一个纯函数,根据供给量和买入或卖出股份的数量计算价格
此函数与 getBuyPrice 函数类似,也是一个视图函数。它通过使用 getPrice 函数和调整后的供给量(在出售指定数量股份后)计算特定股份主题的股份卖出价格
这些函数共同确定股份在 FriendtechSharesV1 合约内的买卖价格。
这一部分的有趣函数是 getPrice 函数。此函数根据当前的股份供给量和评估的股份数量确定股份的价格。
让我们提供一个具体的例子,说明 getPrice 函数如何在给定的供给量和数量值情况下工作。
// 假设我们有以下值并计算价格:
supply = 100
amount = 1
// sum1 针对初始供给量 100 进行计算:
sum1 = (100 - 1) * 100 * (2 * (100 - 1) + 1) / 6 = 161,700
// sum2 当我们考虑到额外的 1 股时进行计算:
sum2 = (100 - 1 + 1) * (100 + 1) * (2 * (100 - 1 + 1) + 1) / 6 = 161,701
// summation 是 sum2 和 sum1 之间的差值:
summation = 161,701 - 161,700 = 1
// 使用 1 ether / 16000 的因子将 summation 转换为 wei:
price = 1 * (1 ether / 16000) = 62,500,000,000,000,000 wei // 0.0625 ETH
因此,当供给量为 100 和额外数量为 1 时,股份的价格为 62,500,000,000,000,000 wei 或 0.0625 ETH。
Friend.tech 中价格与供给量的关系
当股份的供给量(supply
)增加时,每增加一股的价格(amount
)也会增加。此关系是二次的,意味着随着供给量的增长,价格的提高会变得更加陡峭。
如果供给量减少(例如,出售股份),每增加一股的价格将会下降。
价格与供给量之间的关系
正如上面价格与供给量之间的关系所示,getPrice 函数解释了当更多人想购买股份或者更多人想出售股份时,股份价格如何变化。当需求高,买家众多时,价格会上涨,但并不总是以一致的速率上涨。有时会迅速上升,而有时上涨则会缓慢。这些价格变化可能导致股份价值波动,上升或下降。
FriendtechShares-getBuySellPriceAfterFee.sol – Medium
contract FriendtechSharesV1 is Ownable {
// (...SNIPPET...)
function getBuyPriceAfterFee(address sharesSubject, uint256 amount) public view returns (uint256) {
uint256 price = getBuyPrice(sharesSubject, amount);
uint256 protocolFee = price * protocolFeePercent / 1 ether;
uint256 subjectFee = price * subjectFeePercent / 1 ether;
return price + protocolFee + subjectFee;
}
function getSellPriceAfterFee(address sharesSubject, uint256 amount) public view returns (uint256) {
uint256 price = getSellPrice(sharesSubject, amount);
uint256 protocolFee = price * protocolFeePercent / 1 ether;
uint256 subjectFee = price * subjectFeePercent / 1 ether;
return price - protocolFee - subjectFee;
}
// (...SNIPPET...)
}
FriendtechShares-getBuySellPriceAfterFee.sol 通过 GitHub 托管
接下来,我们有两个方便的函数:getBuyPriceAfterFee 和 getSellPriceAfterFee。这些函数旨在让外部服务或应用计算股份的买入和卖出价格,同时考虑到协议和主题费用。
虽然它们不会直接影响合约的内部状态,但它们为用户或外部服务提供了重要的信息,以了解他们将要支付或收到的最终价格,考虑到所有相关费用。
在 FriendtechSharesV1 合约的最后一部分,我们遇到了两个关键函数:buyShares 和 sellShares。
这些函数是合约功能的核心,允许用户通过购买和出售股份与平台进行交互。
FriendtechShares-buyShares.sol – Medium
contract FriendtechSharesV1 is Ownable {
// (...SNIPPET...)
function buyShares(address sharesSubject, uint256 amount) public payable {
uint256 supply = sharesSupply[sharesSubject];
require(supply > 0 sharesSubject == msg.sender, "只有股份的主题可以购买第一股");
uint256 price = getPrice(supply, amount);
uint256 protocolFee = price * protocolFeePercent / 1 ether;
uint256 subjectFee = price * subjectFeePercent / 1 ether;
require(msg.value >= price + protocolFee + subjectFee, "支付不足");
sharesBalance[sharesSubject][msg.sender] = sharesBalance[sharesSubject][msg.sender] + amount;
sharesSupply[sharesSubject] = supply + amount;
emit Trade(msg.sender, sharesSubject, true, amount, price, protocolFee, subjectFee, supply + amount);
(bool success1, ) = protocolFeeDestination.call{value: protocolFee}("");
(bool success2, ) = sharesSubject.call{value: subjectFee}("");
require(success1 && success2, "无法发送资金");
}
// (...SNIPPET...)
}
FriendtechShares-buyShares.sol 通过 GitHub 托管
buyShares 函数允许用户以一定金额的以太购买指定股份主题的股份。该函数计算股份的价格,包括协议和主题费用,并确保发送者提供足够的以太来完成购买。如果支付足够,它将更新用户的股份余额和该主题的股份总供应,记录交易详细信息,并将费用转移到协议和主题。
让我们逐行解析:
检索供应:
uint256 supply = sharesSupply[sharesSubject];
这一行检索指定股份主题的当前股份供应量。
检查有效性:
require(supply > 0 sharesSubject == msg.sender, "只有股份的主题可以购买第一股");
这一行检查供给量是否大于 0 或发送者是否为股份主题。如果供给量为 0(不存在股份)且发送者不是股份主题,则阻止其他任何人购买第一股。
计算价格:
uint256 price = getPrice(supply, amount);
这一行根据当前供给量和希望购买的股份数量使用 getPrice 函数计算股份的价格。
计算协议费用:
uint256 protocolFee = price * protocolFeePercent / 1 ether;
在这里,它根据价格和协议费用百分比计算协议费用。
计算主题费用: uint256 subjectFee = price * subjectFeePercent / 1 ether;
这一行根据价格和主题费用百分比计算主题费用。
检查支付:
require(msg.value >= price + protocolFee + subjectFee, "支付不足");
这一行检查发送者发送的以太(msg.value
)是否大于或等于总价格,包括协议和主题费用。如果支付不足,则函数将不继续。
更新用户余额:
sharesBalance[sharesSubject][msg.sender] = sharesBalance[sharesSubject][msg.sender] + amount;
它更新在指定股份主题内用户的股份余额。
更新供应: sharesSupply[sharesSubject] = supply + amount;
这一行将该股份主题的股份总供应量增加购买的数量。
触发交易事件:
emit Trade(msg.sender, sharesSubject, true, amount, price, protocolFee, subjectFee, supply + amount);
该行触发一个交易事件,记录交易的详细信息,包括发送者、股份主题、交易类型(购买)、购买数量、价格、费用和供应量总和。
转账资金:
(bool success1, ) = protocolFeeDestination.call{value: protocolFee}("");
这一行尝试将协议费用转移到指定的费用目标地址。
转账资金:
(bool success2, ) = sharesSubject.call{value: subjectFee}("");
在这里,它尝试将主题费用转移到股份主题的地址。
最终检查:
require(success1 && success2, "无法发送资金");
这一行检查两个费用转移是否成功。如果任何一个失败,将导致错误。
🚨值得注意的是,buyShares 函数中关于付款的检查状态:
require(msg.value >= price + protocolFee + subjectFee, "支付不足");
如果买家发送的以太(msg.value
)超过交易的总费用(即price + protocolFee + subjectFee
),则合约中不包括将多余的以太退回给买家的机制。
FriendtechShares-sellShares.sol – Medium
contract FriendtechSharesV1 is Ownable {
// (...SNIPPET...)
function sellShares(address sharesSubject, uint256 amount) public payable {
uint256 supply = sharesSupply[sharesSubject];
require(supply > amount, "无法出售最后一股");
uint256 price = getPrice(supply - amount, amount);
uint256 protocolFee = price * protocolFeePercent / 1 ether;
uint256 subjectFee = price * subjectFeePercent / 1 ether;
require(sharesBalance[sharesSubject][msg.sender] >= amount, "股份不足");
sharesBalance[sharesSubject][msg.sender] = sharesBalance[sharesSubject][msg.sender] - amount;
sharesSupply[sharesSubject] = supply - amount;
emit Trade(msg.sender, sharesSubject, false, amount, price, protocolFee, subjectFee, supply - amount);
(bool success1, ) = msg.sender.call{value: price - protocolFee - subjectFee}("");
(bool success2, ) = protocolFeeDestination.call{value: protocolFee}("");
(bool success3, ) = sharesSubject.call{value: subjectFee}("");
require(success1 && success2 && success3, "无法发送资金");
}
// (...SNIPPET...)
}
FriendtechShares-sellShares.sol 通过 GitHub 托管
sellShares 函数使用户能够将其股份出售给合约。此函数对平台的流动性至关重要,允许用户在需要时退出其股份位置。它涉及多个步骤,包括检查资格、计算价格、处理费用和更新余额。
让我们逐行解析:
检索供应:
uint256 supply = sharesSupply[sharesSubject];
这一行检索指定股份主题的当前股份供应量。
检查有效性:
require(supply > amount, "无法出售最后一股");
这一行确保用户不能出售超过可用供给的股份。如果供给量小于或等于一定数量,则将以指定的错误消息中止该交易。
计算价格:
uint256 price = getPrice(supply - amount, amount);
计算出售股份的价格,使用 getPrice 函数考虑到出售指定数量股份后的调整供给量。
计算费用:
uint256 protocolFee = price * protocolFeePercent / 1 ether;
uint256 subjectFee = price * subjectFeePercent / 1 ether;
这两行根据将出售股份的价格计算协议和主题费用。
检查股份余额:
require(sharesBalance[sharesSubject][msg.sender] >= amount, "股份不足");
这一行验证用户是否有足够的股份余额可以出售。如果用户的余额少于他们打算出售的数量,则将使交易回滚。
更新用户余额:
sharesBalance[sharesSubject][msg.sender] = sharesBalance[sharesSubject][msg.sender] - amount;
该函数从用户的余额中扣除出售的股份。
更新供应:
sharesSupply[sharesSubject] = supply - amount;
该行通过减少出售的数量来更新股份主题的总供给量。
触发交易事件:
emit Trade(msg.sender, sharesSubject, false, amount, price, protocolFee, subjectFee, supply - amount);
该行触发事件,记录交易的详细信息,包括卖方、股份主题、交易类型(出售)、出售数量、价格、费用和供应量。
转账资金:
(bool success1, ) = msg.sender.call{value: price - protocolFee - subjectFee}("");
(bool success2, ) = protocolFeeDestination.call{value: protocolFee}("");
(bool success3, ) = sharesSubject.call{value: subjectFee}("");
这些行尝试将收益转移给卖方,将协议费用转移到协议费用目标,并将主题费用转移到主题的地址。
最终检查:
require(success1 && success2 && success3, "无法发送资金");
这一行确保所有资金转移都成功。如果任何一个失败,交易将被中止,防止资金丢失。
在本文中,我们解析了 FriendtechSharesV1 智能合约,揭示了这一创新平台背后的机制。我们探讨了代码,检查了其功能,并解释了它如何在 Friend.tech 独特生态系统中促进交易。
但我们的探索并未结束。如果你对去中心化应用和智能合约的世界感兴趣,请继续关注我们的下一篇文章。直到那时,祝你阅读愉快,学习快乐!
Anak Mirasing,首席区块链安全审计师和顾问
Valix Consulting 是一家区块链和智能合约安全公司,提供广泛的网络安全咨询服务。我们的专家结合了技术专业知识、行业知识和支持人员,努力提供始终如一的优质服务。
如有任何商业咨询,请通过 Twitter、Facebook 或 info@valix.io 联系我们。
- 原文链接: medium.com/valixconsulti...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!