一些应用
位运算
& 按位与 | 按位或 ^ 按位异或 相同为0,不同为1 ~ 按位取反
注意:两种取最后n位值的方法
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract BitwiseOps{
//10011 16 2 1 19
//11001 16 8 1 25
//10001 16 1 17
function and(uint x, uint y) external pure returns(uint){
return x & y;
}
//10011 16 2 1 19
//11001 16 8 1 25
//11011 16 8 2 1 27
function or(uint x, uint y) external pure returns(uint){
return x | y;
}
//00010011 16 2 1 19
//11101100 236
function not(uint8 x) external pure returns(uint8){
return ~x;
}
//10011 16 2 1 19
//11001 16 8 1 25
//01010 8 2 相同为0,不同为1
function xor(uint x, uint y) external pure returns(uint){
return x ^ y;
}
function shiftLeft(uint x, uint bits) external pure returns(uint){
return x << bits;
}
function shiftRight(uint x, uint bits) external pure returns(uint){
return x >> bits;
}
//11001 16 8 1 25
function getLastNBits(uint x, uint n) external pure returns(uint){
uint mask = (1<<n) -1; // 左移n位后再减1,整好得到 0n个1,比如左移5位,就是011111
return x & mask; // 然后这n个一,与原始的数据与,整好把原始数据的后n位给保留下来。
}
function getLastNBitUsingMod(uint x, uint n) external pure returns(uint){
return x % (1<<n); // 只左移n位,得到1n个0,x是这个数的整数倍,整好剩下的就是最后的这几位。整好就是x的余数
} // 10011001101 去后3位,就是 10011001101 % 1000 = 101 这个长的肯定是1000的整数倍的。每往左一位,就是翻倍嘛。
}
键值对传值
关键字传参,用大括号扩上
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract XYZ{
function someFunc(uint x, uint y, address addr1, address addr2, string memory st) private pure returns(uint){}
function callSomeFunc() external pure returns(uint){
return someFunc(1, 2, address(0), address(1), "hello");
}
function callSomeFuncWithKeyValue() external pure returns(uint){
return someFunc({
x:1,
y:2,
addr1:address(1),
addr2:address(2),
st:"hello"
});
}
}
Multi Call
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract TestCall{
function func1() external view returns(uint, uint){
return (1, block.timestamp);
}
function func2() external view returns(uint, uint){
return (2, block.timestamp);
}
function select1() external pure returns(bytes memory){
return abi.encodeWithSelector(this.func1.selector);
}
function select2() external pure returns(bytes memory){
return abi.encodeWithSelector(this.func2.selector);
}
}
contract MultiCll{
function call(address[] calldata arrs, bytes[] calldata funcs) external view returns(bytes[] memory){
require(arrs.length == funcs.length, "contract`s num is not equare funcs`s num");
bytes[] memory results = new bytes[](arrs.length);
for (uint i=0; i< arrs.length; ++i){
(bool success, bytes memory result) = arrs[i].staticcall(funcs[i]);
require(success, "faile");
results[i] = result;
}
return results;
}
}
Multi Delegatecall
59
燃气优化
其实没啥特别的 1、判断的时候,把先判断的条件放在前面,能短路就短路,节省了后面的执行 2、频繁取数组中同一个元素,那就做个临时变量 3、calldata更节省 4、循环中的++i更节省
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Golf{
function option(uint[] calldata arr) external pure returns(uint){
uint res;
uint tmp;
for (uint i=0; i<arr.length; ++i ){
tmp = arr[i];
if (tmp%2 == 0 && tmp < 100){
res += tmp;
}
}
return res;
}
// 2842 i++ 比 ++i 多了一步缓存值的操作,所以就多了点gas,推荐直接用++i
function add1() external pure returns(uint){
uint res;
for (uint i=0; i < 10; i++){
res += i;
}
return res;
}
// 2814
function add2() external pure returns(uint){
uint res;
for (uint i=0; i < 10; ++i){
res += i;
}
return res;
}
}
//[1,2,3,4,5,6,7,8,5,6,44,55,66,34,54,23,56,76,34]
// 15283
// 10653 9729 9657 9562
Access Control
一个访问控制的应用,语法上没有新知识点 案例
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract AccessControl{
mapping(bytes32=>mapping(address=>bool)) public roles;
bytes32 public ADMIN = keccak256(abi.encode("ADMIN"));
bytes32 public USER = keccak256(abi.encode("USER"));
modifier onlyAdmin{
require(roles[ADMIN][msg.sender], "not admin");
_;
}
event RoleGranted(bytes32 role, address account);
event RoleRevoked(bytes32 role, address account);
constructor(){
roles[ADMIN][msg.sender] = true;
}
function grantRole(bytes32 role, address account) external onlyAdmin{
roles[role][account] = true;
emit RoleGranted(role, account);
}
function revokRole(bytes32 role, address account) external onlyAdmin{
roles[role][account] = false;
emit RoleRevoked(role, account);
}
}
查找最大有效位
先判断是否超过某些位,如果超过,直接加上,然后右消除掉这些,然后再做进一步判断
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract MostSignificantBit{
function find(uint x) external pure returns(uint8 r){
if (x > 2**128){
x >> 128;
r += 128;
}
if (x > 2**64){
x >> 64;
r += 64;
}
if (x > 2**32){
x >> 32;
r += 32;
}
if (x > 2**16){
x >> 16;
r += 16;
}
if (x > 2**8){
x >> 8;
r += 8;
}
if (x > 2**4){
x >> 4;
r += 4;
}
if (x > 2**2){
x >> 2;
r += 2;
}
if (x > 2**1){
r += 1;
}
}
}
ERC20规范
规范中定义了6个方法,两个事件, 另外两个铸币与销毁的方法,构造函数中设置符号名字精度 其中转账方法,授权方法与授权转账方法需要手动实现 其中totalSupply(), balanceOf(address account), allowance(address owner, address spender) 方法通过在ERC20合约中,定义状态变量的方式evm自动实现了。 因为public属性,使vem自动创建同名的getter函数 https://docs.soliditylang.org/zh-cn/v0.8.24/contracts.html#getter-functions
uint256 public totalSupply; 这样定义
自动生成下面的方法
function totalSupply() external view returns (uint256) {
return totalSupply;
}
mapping(address => uint256) public balanceOf;
function balanceOf(address account) external view returns (uint256) {
return balanceOf[account];
}
mapping(address => mapping(address => uint256)) public allowance;
function allowance(address owner, address spender) external view returns (uint256) {
return allowance[owner][spender];
}
完整案例
https://solidity-by-example.org/app/erc20/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function allowance(address owner, address spender)
external
view
returns (uint256);
function transfer(address recipient, uint256 amount)
external
returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount)
external
returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(
address indexed owner, address indexed spender, uint256 value
);
}
contract ERC20 is IERC20 {
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
string public name;
string public symbol;
uint8 public decimals;
constructor(string memory _name, string memory _symbol, uint8 _decimals) {
name = _name;
symbol = _symbol;
decimals = _decimals;
}
function transfer(address recipient, uint256 amount)
external
returns (bool)
{
balanceOf[msg.sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(msg.sender, recipient, amount);
return true;
}
function approve(address spender, uint256 amount) external returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transferFrom(address sender, address recipient, uint256 amount)
external
returns (bool)
{
allowance[sender][msg.sender] -= amount;
balanceOf[sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(sender, recipient, amount);
return true;
}
function _mint(address to, uint256 amount) internal {
balanceOf[to] += amount;
totalSupply += amount;
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal {
balanceOf[from] -= amount;
totalSupply -= amount;
emit Transfer(from, address(0), amount);
}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
function burn(address from, uint256 amount) external {
_burn(from, amount);
}
}
ERC721
IERC165
IERC165 定义了一个通用的接口检测标准 https://eips.ethereum.org/EIPS/eip-165
function supportsInterface(bytes4 interfaceID) external view returns (bool);
只用于返回合约实现了那些接口。第三方调用合约的这个方法,来判断这个合约是实现了什么interface的合约
function supportsInterface(bytes4 interfaceId)
external
pure
returns (bool)
{
return interfaceId == type(IERC721).interfaceId
|| interfaceId == type(IERC165).interfaceId;
}
输入某个标准Interface的interfaceId看是否与IERC721,IERC165匹配。 type(...).interfaceId会返回一个 bytes4 值
interfaceId 计算规则
把接口里所有函数的 函数选择器 (selector, bytes4) 做 按位异或 (XOR),得到的结果就是这个接口的 interfaceId。 https://chatgpt.com/s/t_68c0ece991288191890006acf063c395
IERC721Receiver
当你用 safeTransferFrom 把 NFT 转到一个 合约地址 时,会有一个风险:
如果这个合约没有能力处理 ERC721,NFT 就可能永远卡死在里面(无法再转出来)。
interface IERC721Receiver {
function onERC721Received(
address operator, // 调用 safeTransferFrom 的地址
address from, // 原持有人
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
所以,要求任何希望接收 NFT 的合约,必须实现这个函数。 在转账时,ERC721 合约会调用 onERC721Received,并且要求返回该函数的函数选择器。 如果返回值不是 IERC721Receiver.onERC721Received.selector,就会 revert,表示“这个合约不能接收 NFT”。然后整个交易回滚。
safeTransferFrom
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
function safeTransferFrom(address from, address to, uint256 id) external {
transferFrom(from, to, id);
require(
to.code.length == 0
|| IERC721Receiver(to).onERC721Received(msg.sender, from, id, "")
== IERC721Receiver.onERC721Received.selector,
"unsafe recipient"
);
}
区别在于:
第一个版本:只转 NFT,不带额外数据。 第二个版本:允许调用方附带一段任意的 bytes data,传给接收合约。
这就给了扩展空间: 比如,某个 NFT 市场合约在接收 NFT 时,可以通过 data 携带“订单信息、签名、备注”等。 普通钱包只用第一个版本就够了。
为什么是先转了,再检查
首先是,EIP-721 标准中就是这么规定的
这样规定的好处是:确保在检查接收方,调用onERC721Received方法的时候,接收方已经是这个NFT的owner。其实如果检查不通过,其实会revert,整个交易会回滚的。执行完的transferFrom(from, to, id);也就无效了。
完整NFT代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
interface IERC165 {
function supportsInterface(bytes4 interfaceID)
external
view
returns (bool);
}
interface IERC721 is IERC165 {
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function safeTransferFrom(address from, address to, uint256 tokenId)
external;
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function approve(address to, uint256 tokenId) external;
function getApproved(uint256 tokenId)
external
view
returns (address operator);
function setApprovalForAll(address operator, bool _approved) external;
function isApprovedForAll(address owner, address operator)
external
view
returns (bool);
}
interface IERC721Receiver {
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
contract ERC721 is IERC721 {
event Transfer(
address indexed from, address indexed to, uint256 indexed id
);
event Approval(
address indexed owner, address indexed spender, uint256 indexed id
);
event ApprovalForAll(
address indexed owner, address indexed operator, bool approved
);
// Mapping from token ID to owner address 通过id 索引 地址
mapping(uint256 => address) internal _ownerOf;
// Mapping owner address to token count 地址下token的个数
mapping(address => uint256) internal _balanceOf;
// Mapping from token ID to approved address id 授权的 地址
mapping(uint256 => address) internal _approvals;
// Mapping from owner to operator approvals 拥有者 授权 其他地址
mapping(address => mapping(address => bool)) public isApprovedForAll;
function supportsInterface(bytes4 interfaceId)
external
pure
returns (bool)
{
return interfaceId == type(IERC721).interfaceId
|| interfaceId == type(IERC165).interfaceId;
}
function ownerOf(uint256 id) external view returns (address owner) {
owner = _ownerOf[id];
require(owner != address(0), "token doesn't exist");
}
function balanceOf(address owner) external view returns (uint256) {
require(owner != address(0), "owner = zero address");
return _balanceOf[owner];
}
// 把自己的所有token授权给operator操作权限,任何人都可以给任何操作者设置
function setApprovalForAll(address operator, bool approved) external {
isApprovedForAll[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
// 给spender授权某个id的权限。token的拥有着有权限,token的operator也有权限
function approve(address spender, uint256 id) external {
address owner = _ownerOf[id];
require(
msg.sender == owner || isApprovedForAll[owner][msg.sender],
"not authorized"
);
_approvals[id] = spender;
emit Approval(owner, spender, id);
}
function getApproved(uint256 id) external view returns (address) {
require(_ownerOf[id] != address(0), "token doesn't exist");
return _approvals[id];
}
// 要么是owner,要么是授权的operator,要不是授权的spender
function _isApprovedOrOwner(address owner, address spender, uint256 id)
internal
view
returns (bool)
{
return (
spender == owner || isApprovedForAll[owner][spender]
|| spender == _approvals[id]
);
}
// 重要理解:通过_isApprovedOrOwner严格校验了,只有拥有者,和授权spender,或者授权operator可以调用这个方法
function transferFrom(address from, address to, uint256 id) public {
require(from == _ownerOf[id], "from != owner"); // 必须是拥有者
require(to != address(0), "transfer to zero address"); // 不能转给零地址
// 里面对msg.sender作业严格的校验
require(_isApprovedOrOwner(from, msg.sender, id), "not authorized");
_balanceOf[from]--;
_balanceOf[to]++; // 处理余额
_ownerOf[id] = to; // 拥有者
delete _approvals[id]; // 把原本的spender授权清楚掉
emit Transfer(from, to, id);
}
function safeTransferFrom(address from, address to, uint256 id) external {
transferFrom(from, to, id);
require(
to.code.length == 0
|| IERC721Receiver(to).onERC721Received(msg.sender, from, id, "")
== IERC721Receiver.onERC721Received.selector,
"unsafe recipient"
);
}
function safeTransferFrom(
address from,
address to,
uint256 id,
bytes calldata data
) external {
transferFrom(from, to, id);
require(
to.code.length == 0
|| IERC721Receiver(to).onERC721Received(msg.sender, from, id, data)
== IERC721Receiver.onERC721Received.selector,
"unsafe recipient"
);
}
function _mint(address to, uint256 id) internal {
require(to != address(0), "mint to zero address");
require(_ownerOf[id] == address(0), "already minted");
_balanceOf[to]++;
_ownerOf[id] = to;
emit Transfer(address(0), to, id);
}
function _burn(uint256 id) internal {
address owner = _ownerOf[id];
require(owner != address(0), "not minted");
_balanceOf[owner] -= 1;
delete _ownerOf[id];
delete _approvals[id];
emit Transfer(owner, address(0), id);
}
}
contract MyNFT is ERC721 {
function mint(address to, uint256 id) external {
_mint(to, id);
}
function burn(uint256 id) external {
require(msg.sender == _ownerOf[id], "not owner");
_burn(id);
}
}
精简接受NFT的合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/// 最小化 ERC721 接口(只用到 safeTransferFrom)
interface IERC721 {
function safeTransferFrom(address from, address to, uint256 tokenId) external;
}
/// EIP-165 的 ERC721 接收者接口
interface IERC721Receiver {
function onERC721Received(
address operator, // 调用 safeTransferFrom 的地址
address from, // 原持有人
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
/// 标准的“可接收 NFT”合约:什么都不做,只返回 selector,表示我能接
contract ERC721Holder is IERC721Receiver {
function onERC721Received(
address, /* operator */
address, /* from */
uint256, /* tokenId */
bytes calldata /* data */
) external pure override returns (bytes4) {
// 返回固定值:IERC721Receiver.onERC721Received.selector
return IERC721Receiver.onERC721Received.selector;
}
}
最小可接收NFT合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface IERC721 {
function safeTransferFrom(address from, address to, uint256 tokenId) external;
}
interface IERC721Receiver {
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
contract NFTVault is IERC721Receiver {
address public immutable owner;
event Deposited(address indexed nft, uint256 indexed tokenId, address indexed from, bytes data);
event Withdrawn(address indexed nft, uint256 indexed tokenId, address indexed to);
constructor(address _owner) {
owner = _owner;
}
// 标准回调:返回 selector 表示“我能接收”
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external override returns (bytes4) {
emit Deposited(msg.sender, tokenId, from, data);
return IERC721Receiver.onERC721Received.selector;
}
// 由合约 owner 把保管的 NFT 取出转走(也可以改成更复杂的权限)
function withdraw(address nft, uint256 tokenId, address to) external {
require(msg.sender == owner, "not owner");
IERC721(nft).safeTransferFrom(address(this), to, tokenId);
emit Withdrawn(nft, tokenId, to);
}
}
荷兰拍卖
减价拍卖,随时间降价,一次出价成交
设置起始价,持续时长,降价率。保证到拍卖结束,nft价格依旧为正。因为是随时间降价,实际成交的时候可能比决定要买的时候价格更低,所以,需要买家多出价,然后合约要找零给买家。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
interface IERC721 {
function transferFrom (
address _from,
address _to,
uint _nftId
) external;
}
contract DutchAuction {
// NFT 相关信息 immutable 使变量只能设置一次值
IERC721 public immutable nft;
uint public immutable nftId;
// 拍卖信息
uint private constant DURATION = 7 days;
address public immutable seller;
uint public immutable startingPrice;
uint public immutable startAt;
uint public immutable expiresAt;
uint public immutable discountRate;
// 卖家出售 NFT
constructor(
uint _startingPrice,
uint _discountRate,
address _nft,
uint _nftId
)
{
seller = payable(msg.sender);
startingPrice = _startingPrice;
discountRate = _discountRate;
startAt = block.timestamp;
expiresAt = block.timestamp + DURATION;
// 保证等最后拍卖期限,保证nft价格是正的
require( _startingPrice >= _discountRate * DURATION, "starting price < discount");
nft = IERC721(_nft);
nftId = _nftId;
}
// 买家购买 NFT
function buy() external payable {
require(block.timestamp < expiresAt, "aution expired");
uint price = getPrice();
require(msg.value >= price, "ETH < price");
nft.transferFrom(seller, msg.sender, nftId);
uint refund = msg.value - price;
payable(seller).transfer(price); // 给卖家
if(refund > 0) {
payable (msg.sender).transfer(refund); // 找零
}
}
// 查看当前价格
function getPrice() public view returns(uint) {
uint timeElapsed = block.timestamp - startAt;
uint discount = discountRate * timeElapsed; // 根据已持续时长计算减掉的价格
return startingPrice - discount;
}
}
英式拍卖
多次出价,加价拍卖。设置起拍价,拍卖时长。在开始拍卖之前,需要去NFT合约里给拍卖合约授权。
核心逻辑:只能出更高价。每一次出价,把历史价格存储起来,时刻保持最好价和最好出价人在对应变量中。拍卖结束,未拍的账户按照存储的历史价格提款。买到者,合约把NFT给他,然后把钱给卖家。
问题:
同一个账户,不能追加出价,要出只能出最高,历史记录会加和多次出价
转账使用的是transfer
nft转账也没有使用safeTransferFrom
拍卖合约同样也没有实现IERC721Receiver的onERC721Received方法
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
interface IERC721 {
function transferFrom (
address _from,
address _to,
uint _nftId
) external;
}
contract EnglishAuction {
event Start();
event Bid(address indexed sender, uint amount);
event Withdraw(address indexed bidder, uint amount);
event End(address highestBidder, uint highestBid);
// NFT 相关信息
IERC721 public immutable nft;
uint public immutable nftId;
// 拍卖信息
address payable public immutable seller;
uint32 public endAt;
bool public started;
bool public ended;
address public highestBidder;
uint public highestBid;
mapping(address => uint) public bids;
// 初始化
constructor(
address _nft,
uint _nftId,
uint _startingBid // 起拍价
){
nft = IERC721(_nft);
nftId = _nftId;
seller = payable (msg.sender);
highestBid = _startingBid;
}
// 卖家发起拍卖
function start() external {
require(msg.sender == seller, "not seller");
require(!started, "started");
started = true;
endAt = uint32(block.timestamp + 60); // 拍卖时长
nft.transferFrom(seller, address(this), nftId);
emit Start();
}
// 买家竞价
function bid() external payable {
require(started, "not started");
require(block.timestamp < endAt, "ended");
require(msg.value > highestBid, "value < highest bid"); // 只能出更高的钱
if(highestBidder != address(0)) {
bids[highestBidder] += highestBid; // 将历史出价存起来
}
highestBid = msg.value; // 更新最高价
highestBidder = msg.sender; // 更新最高出价人
emit Bid(msg.sender, msg.value);
}
// 买家提款 从历史出价映射里提取
function withdraw() external {
uint bal = bids[msg.sender];
bids[msg.sender] = 0;
payable (msg.sender).transfer(bal); // 不安全,仅仅是示例
emit Withdraw(msg.sender, bal);
}
// 结束拍卖
function end() external {
require(started, "not started");
require(!ended, "ended");
require(block.timestamp >= endAt, "not ended");
ended = true;
if(highestBidder != address(0)) {
nft.transferFrom(address(this), highestBidder, nftId);
seller.transfer(highestBid);
}else{
nft.transferFrom(address(this), seller, nftId);
}
emit End(highestBidder, highestBid);
}
}
时间锁
功能描述
提前设定好执行的方法与参数,到点后,再去执行。 锁合约:参数加密方法。预存任务方法。执行方法。 预存任务的时候,设定可执行任务的未来时间范围。做个限定。 执行方法的时候,要判断已经到了可执行的时间,并且设定一个时间范围,在这个范围内可执行,执行窗口期。 这个设定任务的时间范围,和执行窗口期,并没有直接的关联。
时间控制
在执行合约中设定某些方法的可执行者为时间锁合约,这样就只有时间锁合约可以执行这个合约的知道方法。在时间锁合约中的执行方法中,设定只有预存的方法可以执行。在预存方法中,设定执行的时间。这样,要想执行合约中的方法,就必须在时间锁合约中预存,然后到点才能调用执行。就实现了时间锁功能。
加密方法
不要搞迷糊:keccak256(abi.encode(.....))只是一个普通的哈希。并不和生成calldata的参数编码过程一样,calldata的参数编码是:abi.encode()
function getTxId(
address _target,
uint _value,
string calldata _func,
bytes calldata _data,
uint _timestamp
) public pure returns (bytes32 txId) {
return keccak256(abi.encode(_target, _value, _func, _data, _timestamp));
}
完整代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract TimeLock {
error NotOwnerError();
error AlreadyQueuedError(bytes32 txId);
error TimestampNotInRangeError(uint256 blockTimestamp, uint256 timestamp);
error NotQueuedError(bytes32 txId);
error TimestampNotPassedError(uint256 blockTimestamp, uint256 timestamp);
error TimestampExpiredError(uint256 blockTimestamp, uint256 expiredAt);
error TxFailedError();
event Queue(
bytes32 indexed txId,
address indexed target,
uint256 value,
string func,
bytes data,
uint256 timestamp
);
event Execute(
bytes32 indexed txId,
address indexed target,
uint256 value,
string func,
bytes data,
uint256 timestamp
);
event Cancel(bytes32 indexed txId);
uint256 public constant MIN_DELAY = 10;
uint256 public constant MAX_DELAY = 1000;
uint256 public constant GRACE_PERIOD = 1000;
address public owner;
mapping(bytes32 => bool) public queued;
constructor() {
owner = msg.sender;
}
receive() external payable {}
modifier onlyOwner() {
if (msg.sender != owner) {
revert NotOwnerError();
}
_;
}
function getTxId(
address _target,
uint256 _value,
string calldata _func,
bytes calldata _data,
uint256 _timestamp
) public pure returns (bytes32 txId) {
return keccak256(abi.encode(_target, _value, _func, _data, _timestamp));
}
function queue(
address _target,
uint256 _value,
string calldata _func,
bytes calldata _data,
uint256 _timestamp
) external onlyOwner {
bytes32 txId = getTxId(_target, _value, _func, _data, _timestamp);
if (queued[txId]) {
revert AlreadyQueuedError(txId);
}
// ---|------------|---------------|-------
// block block + min block + max
if (
_timestamp < block.timestamp + MIN_DELAY ||
_timestamp > block.timestamp + MAX_DELAY
) {
revert TimestampNotInRangeError(block.timestamp, _timestamp);
}
// queue tx
queued[txId] = true;
emit Queue(txId, _target, _value, _func, _data, _timestamp);
}
function execute(
address _target,
uint256 _value,
string calldata _func,
bytes calldata _data,
uint256 _timestamp
) external payable onlyOwner returns (bytes memory) {
bytes32 txId = getTxId(_target, _value, _func, _data, _timestamp);
// check tx is queued
if (!queued[txId]) {
revert NotQueuedError(txId);
}
// check block.timestamp > _timestamp
if (block.timestamp < _timestamp) {
revert TimestampNotPassedError(block.timestamp, _timestamp);
}
// ----|-------------------|-------
// timestamp timestamp + grace period
if (block.timestamp > _timestamp + GRACE_PERIOD) {
revert TimestampExpiredError(
block.timestamp,
_timestamp + GRACE_PERIOD
);
}
queued[txId] = false;
bytes memory data;
if (bytes(_func).length > 0) {
data = abi.encodePacked(bytes4(keccak256(bytes(_func))), _data);
} else {
data = _data;
}
// execute the tx
(bool ok, bytes memory res) = _target.call{value: _value}(data);
if (!ok) {
revert TxFailedError();
}
emit Execute(txId, _target, _value, _func, _data, _timestamp);
return res;
}
function cancel(bytes32 _txId) external onlyOwner {
if (!queued[_txId]) {
revert NotQueuedError(_txId);
}
queued[_txId] = false;
emit Cancel(_txId);
}
}
contract TestTimeLock {
address public timeLock;
constructor(address _timeLock) {
timeLock = _timeLock;
}
function test() external {
require(msg.sender == timeLock);
// more code such as
// - 升级合约
// - 转移资产
// - 修改预⾔机
}
function getTimestamp() external view returns (uint256) {
return block.timestamp + 100;
}
}
众筹
发起人,发起多轮筹款,每一轮要单独记录。目标,规定时间内筹到目标金额。如果失败,由出款人主动撤走捐款。
程序设计
筹款轮次:轮次记录在campaigns中。每一次筹款需要有多个属性,来记录筹款信息。Campaign 结构体为一次筹款的主体
struct Campaign{ // 一轮筹款,应该有发起人,目标金额,目前抽到的金额,开始终止时间,是否已经被提取
address creator;
uint goal;
uint pledged;
uint32 startAt;
uint32 endAt;
bool cleamed;
}
mapping(uint => Campaign) public campaigns; // 记录轮次
delegedAmount记录出款人在各个轮次中,所出的金额
mapping(uint => mapping(address => uint)) public delegedAmount; // 记录出款人金额
后续新出款,撤回,失败提取等操作,基本是操作delegedAmount和campaigns的pledged金额。
完整代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface IERC20 {
function transfer(address, uint) external returns(bool);
function transferFrom(address, address, uint) external returns(bool);
}
contract CrowdFund{
IERC20 public immutable token;
struct Campaign{ // 一轮筹款,应该有发起人,目标金额,目前抽到的金额,开始终止时间,是否已经被提取
address creator;
uint goal;
uint pledged;
uint32 startAt;
uint32 endAt;
bool cleamed;
}
mapping(uint => Campaign) public campaigns; // 记录轮次
mapping(uint => mapping(address => uint)) public delegedAmount; // 记录出款人金额
uint public count;
constructor(address _token){
token = IERC20(_token);
}
event Launch(uint count, address creator, uint goal, uint32 startAt, uint32 endAt);
event Cancel(uint count, address creator);
event Pledge(uint _id, address pledger, uint amount);
event Unpledge(uint _id, address unpledger, uint amount);
event Claim(uint _id, address creator, uint amount);
event Refund(uint _id, address refunder, uint amount);
function launch(uint _goal, uint32 startAt, uint32 endAt) public {
require(endAt > startAt, "end time should be late than start time");
require(endAt <= 30 days, "endAt > 30 days");
uint32 startTime = uint32(block.timestamp) + startAt;
uint32 endTime = uint32(block.timestamp) + endAt;
count += 1;
campaigns[count] = Campaign({
creator: msg.sender,
goal: _goal,
pledged: 0,
startAt: startTime,
endAt: endTime,
cleamed: false
});
emit Launch(count, msg.sender, _goal, startTime, endTime);
}
// 只有创建人可以取消,并且是在筹款还没有开始的时候。
function cancel(uint _id) public {
Campaign memory tmp = campaigns[_id];
require(block.timestamp < tmp.startAt, "this campaign is started");
require(msg.sender == tmp.creator, "only creator can do this");
delete campaigns[_id]; // 冲轮次记录中,直接删掉该轮次id
emit Cancel(_id, msg.sender);
}
// 给第几轮,出款。要保证该轮次已经开始了。
function pledge(uint _id, uint amount) public {
Campaign memory tmp = campaigns[_id];
require(block.timestamp >= tmp.startAt, "this campaign is not start");
require(block.timestamp <= tmp.endAt, "this campaign is closed");
campaigns[_id].pledged += amount; // 记录总筹款金额
delegedAmount[_id][msg.sender] += amount; // 记录出款人的金额
token.transferFrom(msg.sender, address(this), amount); // 转给合约
emit Pledge(_id, msg.sender, amount);
}
// 在该轮次还没有结束时,可以撤回出款,肯定你出回去的钱,肯定要小于你出的钱
function unpledge(uint _id, uint amount) external {
Campaign memory tmp = campaigns[_id];
require(block.timestamp <= tmp.endAt, "this campaign is closed");
require(delegedAmount[_id][msg.sender]>= amount, "Insufficient limit");
campaigns[_id].pledged -= amount;
delegedAmount[_id][msg.sender] -= amount;
token.transfer(msg.sender, amount);
emit Unpledge(_id, msg.sender, amount);
}
// 保证是发起人提的,保证已经结束,已经达到目标金额,并且只能提取一次
function claim(uint _id) external{
Campaign memory tmp = campaigns[_id];
require(tmp.creator == msg.sender, "only creator can claim");
require(block.timestamp > tmp.endAt, "this campaign is not closed");
require(tmp.pledged >= tmp.goal, "Failed to achieve the goal, unable to extract");
require(!tmp.cleamed, "claimed");
campaigns[_id].cleamed = true;
token.transfer(msg.sender, tmp.pledged);
emit Claim(_id, msg.sender, tmp.pledged);
}
// 在筹款没有达到目标金额的情况下。出款人,主动提取之前已出的款
function refund(uint _id) external {
Campaign memory tmp = campaigns[_id];
require(block.timestamp > tmp.endAt, "this campaign is not closed");
require(tmp.pledged < tmp.goal, "to claim");
uint amount = delegedAmount[_id][msg.sender];
delegedAmount[_id][msg.sender] = 0;
token.transfer(msg.sender, amount);
emit Refund(_id, msg.sender, amount);
}
}
多签钱包
52
WETH
其实就是一个ERC20,接收eth,然后产生该ERC20代币
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract WETH is ERC20 {
event Deposit(address indexed account, uint amount);
event Withdraw(address indexed account, uint amount);
constructor() ERC20("Wrapped Ether", "WETH") {}
receive() external payable {
deposit();
}
fallback() external payable {
deposit();
}
function deposit() public payable {
_mint(msg.sender, msg.value);
emit Deposit(msg.sender, msg.value);
}
function withdraw(uint _amount) external {
_burn(msg.sender, _amount);
payable(msg.sender).transfer(_amount);
emit Withdraw(msg.sender, _amount);
}
}