bodhi菩提项目是一个用户可以创建贴文,同时可以对贴文进行买卖股份share的平台。本文将对项目合约进行代码详解
官网地址:https://bodhi.wtf/ 合约地址:https://optimistic.etherscan.io/address/0x2ad82a4e39bac43a54ddfe6f94980aaf0d1409ef#code
import {ERC1155} from "solmate/src/tokens/ERC1155.sol";
合约调用ERC1155.sol标准合约,与标准合约ERC721不一样的地方是,721类型NFT每个ID仅此有一个,1155类型NFT是相同的ID可以有多个数量
举个例子,这里有ID为11的NFT,点击buy Share的话,相当于在ERC1155这一系列的NFT里面的第11号ID购买了Share,可多人持有该ID的Share,而ERC721中同一ID只能一人独有
event Create(uint256 indexed assetId, address indexed sender, string arTxId);
event Remove(uint256 indexed assetId, address indexed sender);
event Trade(
TradeType indexed tradeType,
uint256 indexed assetId,
address indexed sender,
uint256 tokenAmount,
uint256 ethAmount,
uint256 creatorFee
);
event类型Create、Remove、Trade三个是用于日志的一个记录,允许外部程序监听合约中的状态变化
struct Asset {
uint256 id;
string arTxId; // arweave transaction id
address creator;
}
每篇文章都有的三个信息:id、arTxId、creator 1.id为文章的id 2.arTxId是指 Arweave 上的交易 ID,Arweave 是一个永久性的去中心化存储网络,它专注于存储数据。arTxId作用是将菩提上的文章存储在Arweave上,即Arweave存储空间的ID。只要在Arweave上输入arTxId就能获得相对应的贴文内容。
3.creator为文章的创造者
uint256 public assetIndex;
assetIndex:表示当前已创建的贴文数量,它是一个计数器。通过观察合约信息,可以得知目前菩提有13989篇贴文(第一篇序号为0)
mapping(uint256 => Asset) public assets;
映射:输入一个uint256类型的ID,就返回整个Asset内容
mapping(address => uint256[]) public userAssets;
映射:输入一个address地址,返回用户所创建的Assert ID。应用场景:My Holdings界面,“我”创建了哪些贴文的ID,方便用户查询自己的贴文
mapping(bytes32 => uint256) public txToAssetId;
映射:传入哈希值(bytes32类型)返回一个贴文的ID号(uint256类型),这用来在创建贴文时用于检查贴文是否已经存在,以避免重复创建相同哈希值的贴文
mapping(uint256 => uint256) public totalSupply;
totalSupply总供应量:贴文目前总共的购买share的数量,每有人buy,totalSupply就会增加
mapping(uint256 => uint256) public pool;
pool 被用来记录每个资产的池(pool)总额,其中贴文的唯一标识符(assetId)用作键,而池的总额用作相应的值,当进行买卖交易时,买家支付的 ETH 会被添加到相应贴文的池中,这样卖家就可以从池中提取相应的 ETH
uint256 public constant CREATOR_PREMINT = 1 ether; // 1e18
CREATOR_PREMINT:用于精确计算,比如3除以2,在区块链上是等于1,因为区块链上只能整数运算,与1.5差距较大,用3*CREATOR_PREMINT=3乘10的18次方再除以2,把数字放大再进行加减乘除,这样计算就增强了精确性。
uint256 public constant CREATOR_FEE_PERCENT = 0.05 ether; // 5%
CREATOR_FEE_PERCENT:购买贴文Share收取的手续费百分比,手续费会转给创建贴文的用户
enum TradeType {
Mint,
Buy,
Sell
} // = 0, 1, 2
枚举类型(enum)允许定义一组命名的常量值,这里TradeType是一个枚举类型,它定义了三个可能的交易类型:Mint(铸造)、Buy(购买)和Sell(出售),可以使用TradeType.Mint来表示铸造交易类型,TradeType.Buy表示购买交易类型,TradeType.Sell表示出售交易类型
function create(string calldata arTxId) public
传入arTxId,使用create函数就能创建贴文
bytes32 txHash = keccak256(abi.encodePacked(arTxId));
将arTxId用abi.encodePacked()进行编码后方便keccak256()进行哈希运算,得到txHash这个变量
require(txToAssetId[txHash] == 0, "Asset already exists");
如果txHash对应的资产ID不为0,那么说明这个哈希值已经存在于txToAssetId映射中,即已经有一个相同的资产使用了这个哈希值,用来确保用户不会重复创建相同的贴文
uint256 newAssetId = assetIndex;
将新的可用的贴文ID分配给了新创建的贴文,下方图片左上角的0就是贴文ID,新的贴文就依次加一
assets[newAssetId] = Asset(newAssetId, arTxId, msg.sender);
将创建的新贴文存储在assets映射中,存储的键为newAssetId,存储的键对应值为newAssetId, arTxId, msg.sender(创建新贴文用户的钱包地址)
userAssets[msg.sender].push(newAssetId);
将新创建的贴文ID添加到当前用户(msg.sender)的userAssets数组中
txToAssetId[txHash] = newAssetId;
将新创建贴文的newAssetId与arTxId的哈希值txHash关联起来,存入txToAssetId映射中
totalSupply[newAssetId] += CREATOR_PREMINT;
新贴文的初始总供应量设置为CREATOR_PREMINT(10的18次方),方便小数点的计算
assetIndex = newAssetId + 1;
这是跟上方assets[newAssetId] = Asset(newAssetId, arTxId, msg.sender);
配合在一起用的,使创建的新贴文ID是没有用过的最新ID
_mint(msg.sender, newAssetId, CREATOR_PREMINT, "");
_mint方法是ERC1155标准合约中定义好的函数,用来铸造NFT,每创建新贴文就是在铸造新NFT。CREATOR_PREMINT为新帖文的初始供应量。"" 是一个空字符串,用于传递额外的数据。
emit Create(newAssetId, msg.sender, arTxId);
emit Trade(TradeType.Mint, newAssetId, msg.sender, CREATOR_PREMINT, 0, 0);
每创建新贴文,都会有Create和Trade事件触发,记录日志
function getAssetIdsByAddress(address addr) public view returns (uint256[] memory) {
return userAssets[addr];
}
getAssetIdsByAddress:传入用户的钱包地址address返回用户所创建的所有贴文ID数组
function _curve(uint256 x) private pure returns (uint256) {
return x <= CREATOR_PREMINT ? 0 : ((x - CREATOR_PREMINT) * (x - CREATOR_PREMINT) * (x - CREATOR_PREMINT));
}
这个函数用于计算贴文价格曲线,根据贴文的供应量来确定资产的价格。函数意思是如果 x(贴文供应量)小于或等于预分配给创建者的数量 CREATOR_PREMINT,则返回0,否则返回 (x - CREATOR_PREMINT) (x - CREATOR_PREMINT) (x - CREATOR_PREMINT) 的计算结果。
function getPrice(uint256 supply, uint256 amount) public pure returns (uint256) {
return (_curve(supply + amount) - _curve(supply)) / 1 ether / 1 ether / 50_000;
}
这个函数主要用于计算基于贴文供应量和交易数量的价格。(_curve(supply + amount) - _curve(supply)) 计算了交易后资产价格曲线的变化量。/ 1 ether / 1 ether / 50_000 对价格曲线的变化量进行了一系列除法操作,以确定最终的价格。
function getBuyPrice(uint256 assetId, uint256 amount) public view returns (uint256) {
return getPrice(totalSupply[assetId], amount);
}
函数用于获取购买指定share的贴文的价格,其计算方式依赖于 getPrice 函数的实现,并根据资产的总供应量和购买的share来确定价格。这跟上方的getPrice区别在于:第一个函数用于内部计算资产价格曲线的变化,而第二个函数则用于外部获取购买资产的价格。
function getSellPrice(uint256 assetId, uint256 amount) public view returns (uint256) {
return getPrice(totalSupply[assetId] - amount, amount);
}
函数用于获取销售指定share的贴文的价格,其计算方式依赖于 getPrice 函数的实现,并根据贴文的总供应量和销售的share来确定价格。
function getBuyPriceAfterFee(uint256 assetId, uint256 amount) public view returns (uint256) {
uint256 price = getBuyPrice(assetId, amount);
uint256 creatorFee = (price * CREATOR_FEE_PERCENT) / 1 ether;
return price + creatorFee;
}
函数用来计算用户实际购买贴文share的价格,等于贴文share价格加上手续费,手续费是价格的5%
function getSellPriceAfterFee(uint256 assetId, uint256 amount) public view returns (uint256) {
uint256 price = getSellPrice(assetId, amount);
uint256 creatorFee = (price * CREATOR_FEE_PERCENT) / 1 ether;
return price - creatorFee;
}
函数用来计算用户实际售卖贴文share的价格,等于贴文share价格减去手续费
function buy(uint256 assetId, uint256 amount) public payable {
require(assetId < assetIndex, "Asset does not exist");
贴文ID要小于计数器assetId,限制用户不能提前购买还未创建的贴文Share。例如,目前有8000条贴文,我购买assetId为9000的贴文的Share,目前还没有assetId为9000的贴文,所以要做限制
uint256 price = getBuyPrice(assetId, amount);
uint256 creatorFee = (price * CREATOR_FEE_PERCENT) / 1 ether;
获取价格和手续费
require(msg.value >= price + creatorFee, "Insufficient payment");
用户购买付出的金额要大于等于价格与手续费的和
totalSupply[assetId] += amount;
用户购买多少Share,该贴文的总供应量就增加多少
pool[assetId] += price;
用户购买贴文Share后,pool池子中增加相应的价格
_mint(msg.sender, assetId, amount, "");
用户购买贴文Share后,利用ERC1155 合约的mint函数铸造该贴文相应数量的NFT
emit Trade(TradeType.Buy, assetId, msg.sender, amount, price, creatorFee);
触发Trade事件,记录交易细节,包括交易类型 (tradeType)、贴文ID (assetId)、用户钱包地址(msg.sender)、代币数量 (amount)、价格(price) 和手续费 (creatorFee)
(bool creatorFeeSent, ) = payable(assets[assetId].creator).call{value: creatorFee}("");
将用户购买所付的手续费发送给创作者的钱包的地址,并记录是否发送成功。 1.(bool creatorFeeSent, ) = ...:这是一个解构语法,将 call 函数的返回值中的第一个返回值(表示是否成功发送以太币)分配给 creatorFeeSent 变量,而第二个返回值被忽略。 2.assets[assetId].creator:获取贴文的创建者钱包地址。 3.payable(assets[assetId].creator):将地址转换为 payable 类型,以便进行以太币转账。 4..call{value: creatorFee}(""):以太币调用,使用 call 函数向合约地址发送以太币,其中 {value: creatorFee} 表示要发送的以太币数量为 creatorFee。空字符串 "" 是传递给被调用合约的数据,因为这里没有需要传递的额外数据。
require(creatorFeeSent, "Failed to send Ether");
如果尝试向贴文的创建者发送费用的操作失败(即 creatorFeeSent 为 false),则函数执行会被中止,并返回错误消息 "Failed to send Ether"
function sell(uint256 assetId, uint256 amount) public {
require(assetId < assetIndex, "Asset does not exist");
require(balanceOf[msg.sender][assetId] >= amount, "Insufficient balance");
uint256 supply = totalSupply[assetId];
require(supply - amount >= CREATOR_PREMINT, "Supply not allowed below premint amount");
uint256 price = getSellPrice(assetId, amount);
uint256 creatorFee = (price * CREATOR_FEE_PERCENT) / 1 ether;
_burn(msg.sender, assetId, amount);
totalSupply[assetId] = supply - amount;
pool[assetId] -= price;
emit Trade(TradeType.Sell, assetId, msg.sender, amount, price, creatorFee);
(bool sent, ) = payable(msg.sender).call{value: price - creatorFee}("");
(bool creatorFeeSent, ) = payable(assets[assetId].creator).call{value: creatorFee}("");
require(sent && creatorFeeSent, "Failed to send Ether");
}
sell函数与buy函数的逻辑基本相同,下面介绍几个不同的地方
require(balanceOf[msg.sender][assetId] >= amount, "Insufficient balance");
用户卖的数量不能超过拥有的数量 1.balanceOf是ERC1155合约中定义的映射,函数定义: mapping(address => mapping(uint256 => uint256)) public balanceOf; 2.这个映射将账户地址映射到一个包含贴文ID和余额之间关系的映射 3.balanceOf[msg.sender][assetId] 就表示了账户 msg.sender 在贴文assetId 上的余额。
require(supply - amount >= CREATOR_PREMINT, "Supply not allowed below premint amount");
确保从资产的总供应量中减去用户卖的数量amount 后,剩余的供应量不会低于预先铸造的数量 CREATOR_PREMINT
_burn(msg.sender, assetId, amount);
用户售卖后,销毁相应数量的NFT
(bool sent, ) = payable(msg.sender).call{value: price - creatorFee}("");
用户售卖后,向用户转账金额,金额为价格减去手续费
function uri(uint256 id) public view override returns (string memory) {
return assets[id].arTxId;
}
uri:传入文章ID,返回存储在Arweave中文章的arTxId
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!