Smart Contract Developer
EIP-712 使用详解
之前的文章我们介绍过如何对数据进行签名,利用签名技术我们可以实现一些功能例如白名单校验等。但是这种签名技术的应用场景比较简单,一般就是给一串字符串,或者一串哈希签名,如果我们想为更复杂的数据签名就无法实现了。 EIP-712 的出现就是为了解决这个问题,利用 EIP-712,我们可以对更大的数据集,例如对结构体进行签名。那么这种签名格式有什么实际的应用场景呢。使用过 Uniswap,PancakeSwap 等 DEX 的朋友应该有印象,在移除 LP 流动性的时候,我们需要先签名,然后再发送一笔交易移除流动性。正常情况下,其实应该我们先调用 LP 代币的授权方法,授权 DEX 合约可以转移我们的 LP,然后再去移除流动性。而这种二合一的实现正是应用了 EIP-712。它帮助我们仅仅签名一次,就可以将两步交易合并为一步交易,从而节省 Gas 费用。这篇文章我们就来看看 EIP-712 到底是怎么使用的。基本结构EIP712Domain顾名思义,是一个与域相关的结构体,总共包含五个字段:name,合约或者协议的名称version,合约的版本chainId,合约部署的链 Id,一般使用 ...
流动性挖矿-合约原理详解
流动性挖矿应该是上个牛市最火热的内容,基本上整个 DeFi 都是在围绕着流动性挖矿展开的,今天我们就来看看它到底是什么以及合约代码层面是怎么实现的。流动性挖矿简介首先我们先从用户的角度来理解一下流动性挖矿是什么,实际上就是用户通过在合约中质押一个 token 从而赚取另一个 token 的过程。例如,SushiSwap 最初推出的 DEX 流动性挖矿,用户可以通过将 SushiSwap 的 LP token 质押到合约中赚取 Sushi token。那么这个奖励具体是怎么发放以及如何实现的呢?我们今天就来研究一下这部分内容。 先来看几个例子: 一:假设有一个流动性挖矿的合约,可以质押 A token 赚取 B token。它在 0 秒时开始活动,每秒奖励 R 个 B token。此时有用户 Alice 在第 3 秒时质押了 2 个 A token,并且之后没有其他人参与,在第 8 秒时取出 token,图示:那么他在此时获得的收益就是:5R = (2 / 2) * (8 - 3) * R 其中,第一个 2 是用户 A 质押的数量,第二个 2 是合约中质押的总量,(8-3)是用户 ...
CREATE2 操作码使用方法详解
CREATE2 是一个可以在合约中创建合约的操作码。我们先来举个例子看看它能干什么:这段代码是 Uniswap v2-core 里面的工厂合约代码,使用 create2 操作码创建了 pair 合约,返回值是 pair 的地址,这样就可以逻辑中直接使用其地址进行接下来的操作。 那么 create2 到底是怎么使用呢,根据官方 EIP 文档,create2 一共接收四个参数,分别是:endowment(创建合约时往合约中打的 ETH 数量)memory_start(代码在内存中的起始位置,一般固定为 add(bytecode, 0x20) )memory_length(代码长度,一般固定为 mload(bytecode) )salt(随机数盐)这里要注意的是第一个参数如果大于 0 的话,需要待部署合约的构造方法带有 payable。随机数盐是由用户自定,须为 bytes32 格式,例如在上面 Uniswap 的例子中,salt 为:bytes32 salt = keccak256(abi.encodePacked(token0, token1)); create2 还有一个优点,相...
EIP-712 使用详解
之前的文章我们介绍过如何对数据进行签名,利用签名技术我们可以实现一些功能例如白名单校验等。但是这种签名技术的应用场景比较简单,一般就是给一串字符串,或者一串哈希签名,如果我们想为更复杂的数据签名就无法实现了。 EIP-712 的出现就是为了解决这个问题,利用 EIP-712,我们可以对更大的数据集,例如对结构体进行签名。那么这种签名格式有什么实际的应用场景呢。使用过 Uniswap,PancakeSwap 等 DEX 的朋友应该有印象,在移除 LP 流动性的时候,我们需要先签名,然后再发送一笔交易移除流动性。正常情况下,其实应该我们先调用 LP 代币的授权方法,授权 DEX 合约可以转移我们的 LP,然后再去移除流动性。而这种二合一的实现正是应用了 EIP-712。它帮助我们仅仅签名一次,就可以将两步交易合并为一步交易,从而节省 Gas 费用。这篇文章我们就来看看 EIP-712 到底是怎么使用的。基本结构EIP712Domain顾名思义,是一个与域相关的结构体,总共包含五个字段:name,合约或者协议的名称version,合约的版本chainId,合约部署的链 Id,一般使用 ...
流动性挖矿-合约原理详解
流动性挖矿应该是上个牛市最火热的内容,基本上整个 DeFi 都是在围绕着流动性挖矿展开的,今天我们就来看看它到底是什么以及合约代码层面是怎么实现的。流动性挖矿简介首先我们先从用户的角度来理解一下流动性挖矿是什么,实际上就是用户通过在合约中质押一个 token 从而赚取另一个 token 的过程。例如,SushiSwap 最初推出的 DEX 流动性挖矿,用户可以通过将 SushiSwap 的 LP token 质押到合约中赚取 Sushi token。那么这个奖励具体是怎么发放以及如何实现的呢?我们今天就来研究一下这部分内容。 先来看几个例子: 一:假设有一个流动性挖矿的合约,可以质押 A token 赚取 B token。它在 0 秒时开始活动,每秒奖励 R 个 B token。此时有用户 Alice 在第 3 秒时质押了 2 个 A token,并且之后没有其他人参与,在第 8 秒时取出 token,图示:那么他在此时获得的收益就是:5R = (2 / 2) * (8 - 3) * R 其中,第一个 2 是用户 A 质押的数量,第二个 2 是合约中质押的总量,(8-3)是用户 ...
CREATE2 操作码使用方法详解
CREATE2 是一个可以在合约中创建合约的操作码。我们先来举个例子看看它能干什么:这段代码是 Uniswap v2-core 里面的工厂合约代码,使用 create2 操作码创建了 pair 合约,返回值是 pair 的地址,这样就可以逻辑中直接使用其地址进行接下来的操作。 那么 create2 到底是怎么使用呢,根据官方 EIP 文档,create2 一共接收四个参数,分别是:endowment(创建合约时往合约中打的 ETH 数量)memory_start(代码在内存中的起始位置,一般固定为 add(bytecode, 0x20) )memory_length(代码长度,一般固定为 mload(bytecode) )salt(随机数盐)这里要注意的是第一个参数如果大于 0 的话,需要待部署合约的构造方法带有 payable。随机数盐是由用户自定,须为 bytes32 格式,例如在上面 Uniswap 的例子中,salt 为:bytes32 salt = keccak256(abi.encodePacked(token0, token1)); create2 还有一个优点,相...
Smart Contract Developer

Subscribe to xyyme.eth

Subscribe to xyyme.eth
Share Dialog
Share Dialog
<100 subscribers
<100 subscribers
上周 BAYC 发币,引起了一阵热度。这篇文章,就来看看 APE 空投合约的代码。
先来看看官网上空投的规则:

注意到,必须拥有 BAYC 或者 MAYC 才能够领取空投。而仅仅拥有 Kennel Club ,是不能够领取的,必须搭配前两者才能领取。
接下来看看代码。
数据结构:
// APE 币
IERC20 public immutable grapesToken;
// BAYC nft
ERC721Enumerable public immutable alpha;
// MAYC nft
ERC721Enumerable public immutable beta;
// Kennel Club nft
ERC721Enumerable public immutable gamma;
// BAYC可以领取的数量:10094
uint256 public immutable ALPHA_DISTRIBUTION_AMOUNT;
// MAYC可以领取的数量:2042
uint256 public immutable BETA_DISTRIBUTION_AMOUNT;
// Kennel Club可以领取的数量:856
uint256 public immutable GAMMA_DISTRIBUTION_AMOUNT;
// 总共领取了多少token
uint256 public totalClaimed;
// 领取时间:7776000秒 -> 90 天
uint256 public claimDuration;
// 开始领取时间:2022-03-17 20:08:07(北京时间)
uint256 public claimStartTime;
// 记录这些 nft id 是否被领取过
// BAYC
mapping (uint256 => bool) public alphaClaimed;
// MAYC
mapping (uint256 => bool) public betaClaimed;
// Kennel Club
mapping (uint256 => bool) public gammaClaimed;
这些数据都是在构造方法中直接赋值。
下面的方法计算用户可以领取的数量:
// 计算可以领取的数量
function getClaimableTokenAmount(address _account) public view returns (uint256) {
uint256 tokensAmount;
(tokensAmount,) = getClaimableTokenAmountAndGammaToClaim(_account);
return tokensAmount;
}
function getClaimableTokenAmountAndGammaToClaim(address _account) private view returns (uint256, uint256)
{
// 计算可以领取token的BAYC有效数量
uint256 unclaimedAlphaBalance;
for(uint256 i; i < alpha.balanceOf(_account); ++i) {
uint256 tokenId = alpha.tokenOfOwnerByIndex(_account, i);
// 如果该id已经领取过,则跳过
if(!alphaClaimed[tokenId]) {
++unclaimedAlphaBalance;
}
}
// 计算可以领取token的MAYC有效数量
uint256 unclaimedBetaBalance;
for(uint256 i; i < beta.balanceOf(_account); ++i) {
uint256 tokenId = beta.tokenOfOwnerByIndex(_account, i);
// 如果该id已经领取过,则跳过
if(!betaClaimed[tokenId]) {
++unclaimedBetaBalance;
}
}
// 计算可以领取token的Kennel Club有效数量
uint256 unclaimedGamaBalance;
for(uint256 i; i < gamma.balanceOf(_account); ++i) {
uint256 tokenId = gamma.tokenOfOwnerByIndex(_account, i);
if(!gammaClaimed[tokenId]) {
++unclaimedGamaBalance;
}
}
// 我们前面说过,Kennel Club必须搭配BAYC或者MAYC才能领取
// 仅仅拥有Kennel Club不可以领取
// 这里就是对这个条件进行计算
uint256 gammaToBeClaim = min(unclaimedAlphaBalance + unclaimedBetaBalance, unclaimedGamaBalance);
// 计算出用户可以领取的token总量
uint256 tokensAmount = (unclaimedAlphaBalance * ALPHA_DISTRIBUTION_AMOUNT)
+ (unclaimedBetaBalance * BETA_DISTRIBUTION_AMOUNT) + (gammaToBeClaim * GAMMA_DISTRIBUTION_AMOUNT);
// 返回的两个参数分别为:
// 1.可以领取的token数量
// 2.与前两种nft搭配的Kennel Club配对的数量
return (tokensAmount, gammaToBeClaim);
}
用户领取token的方法:
function claimTokens() external whenNotPaused {
// 校验当前时间在有效时间区间内
require(block.timestamp >= claimStartTime && block.timestamp < claimStartTime + claimDuration, "Claimable period is finished");
// 校验用户拥有有效nft
require((beta.balanceOf(msg.sender) > 0 || alpha.balanceOf(msg.sender) > 0), "Nothing to claim");
uint256 tokensToClaim;
uint256 gammaToBeClaim;
// 根据上面的方法,得到用户可以领取的数量
(tokensToClaim, gammaToBeClaim) = getClaimableTokenAmountAndGammaToClaim(msg.sender);
// 更新BAYC的领取数据并发送事件
for(uint256 i; i < alpha.balanceOf(msg.sender); ++i) {
uint256 tokenId = alpha.tokenOfOwnerByIndex(msg.sender, i);
if(!alphaClaimed[tokenId]) {
alphaClaimed[tokenId] = true;
emit AlphaClaimed(tokenId, msg.sender, block.timestamp);
}
}
// 更新MAYC的领取数据并发送事件
for(uint256 i; i < beta.balanceOf(msg.sender); ++i) {
uint256 tokenId = beta.tokenOfOwnerByIndex(msg.sender, i);
if(!betaClaimed[tokenId]) {
betaClaimed[tokenId] = true;
emit BetaClaimed(tokenId, msg.sender, block.timestamp);
}
}
// 更新Kennel Club的领取数据并发送事件
uint256 currentGammaClaimed;
for(uint256 i; i < gamma.balanceOf(msg.sender); ++i) {
uint256 tokenId = gamma.tokenOfOwnerByIndex(msg.sender, i);
// 注意这里是根据Kennel Club nft配对数量进行计算
if(!gammaClaimed[tokenId] && currentGammaClaimed < gammaToBeClaim) {
gammaClaimed[tokenId] = true;
emit GammaClaimed(tokenId, msg.sender, block.timestamp);
currentGammaClaimed++;
}
}
grapesToken.safeTransfer(msg.sender, tokensToClaim);
totalClaimed += tokensToClaim;
emit AirDrop(msg.sender, tokensToClaim, block.timestamp);
}
其他的方法例如,管理员转回未领取的token等,都比较简单,这里不再赘述。
APE 的空投合约相对来说比较简单,它有一个特点是根据用户的 NFT 实时持仓来进行空投,而不是在某一个区块或时间点进行快照。而这一点恰恰造成了套利的机会,有科学家根据这个特点进行闪电贷套利,获取了大量的 ETH。
上周 BAYC 发币,引起了一阵热度。这篇文章,就来看看 APE 空投合约的代码。
先来看看官网上空投的规则:

注意到,必须拥有 BAYC 或者 MAYC 才能够领取空投。而仅仅拥有 Kennel Club ,是不能够领取的,必须搭配前两者才能领取。
接下来看看代码。
数据结构:
// APE 币
IERC20 public immutable grapesToken;
// BAYC nft
ERC721Enumerable public immutable alpha;
// MAYC nft
ERC721Enumerable public immutable beta;
// Kennel Club nft
ERC721Enumerable public immutable gamma;
// BAYC可以领取的数量:10094
uint256 public immutable ALPHA_DISTRIBUTION_AMOUNT;
// MAYC可以领取的数量:2042
uint256 public immutable BETA_DISTRIBUTION_AMOUNT;
// Kennel Club可以领取的数量:856
uint256 public immutable GAMMA_DISTRIBUTION_AMOUNT;
// 总共领取了多少token
uint256 public totalClaimed;
// 领取时间:7776000秒 -> 90 天
uint256 public claimDuration;
// 开始领取时间:2022-03-17 20:08:07(北京时间)
uint256 public claimStartTime;
// 记录这些 nft id 是否被领取过
// BAYC
mapping (uint256 => bool) public alphaClaimed;
// MAYC
mapping (uint256 => bool) public betaClaimed;
// Kennel Club
mapping (uint256 => bool) public gammaClaimed;
这些数据都是在构造方法中直接赋值。
下面的方法计算用户可以领取的数量:
// 计算可以领取的数量
function getClaimableTokenAmount(address _account) public view returns (uint256) {
uint256 tokensAmount;
(tokensAmount,) = getClaimableTokenAmountAndGammaToClaim(_account);
return tokensAmount;
}
function getClaimableTokenAmountAndGammaToClaim(address _account) private view returns (uint256, uint256)
{
// 计算可以领取token的BAYC有效数量
uint256 unclaimedAlphaBalance;
for(uint256 i; i < alpha.balanceOf(_account); ++i) {
uint256 tokenId = alpha.tokenOfOwnerByIndex(_account, i);
// 如果该id已经领取过,则跳过
if(!alphaClaimed[tokenId]) {
++unclaimedAlphaBalance;
}
}
// 计算可以领取token的MAYC有效数量
uint256 unclaimedBetaBalance;
for(uint256 i; i < beta.balanceOf(_account); ++i) {
uint256 tokenId = beta.tokenOfOwnerByIndex(_account, i);
// 如果该id已经领取过,则跳过
if(!betaClaimed[tokenId]) {
++unclaimedBetaBalance;
}
}
// 计算可以领取token的Kennel Club有效数量
uint256 unclaimedGamaBalance;
for(uint256 i; i < gamma.balanceOf(_account); ++i) {
uint256 tokenId = gamma.tokenOfOwnerByIndex(_account, i);
if(!gammaClaimed[tokenId]) {
++unclaimedGamaBalance;
}
}
// 我们前面说过,Kennel Club必须搭配BAYC或者MAYC才能领取
// 仅仅拥有Kennel Club不可以领取
// 这里就是对这个条件进行计算
uint256 gammaToBeClaim = min(unclaimedAlphaBalance + unclaimedBetaBalance, unclaimedGamaBalance);
// 计算出用户可以领取的token总量
uint256 tokensAmount = (unclaimedAlphaBalance * ALPHA_DISTRIBUTION_AMOUNT)
+ (unclaimedBetaBalance * BETA_DISTRIBUTION_AMOUNT) + (gammaToBeClaim * GAMMA_DISTRIBUTION_AMOUNT);
// 返回的两个参数分别为:
// 1.可以领取的token数量
// 2.与前两种nft搭配的Kennel Club配对的数量
return (tokensAmount, gammaToBeClaim);
}
用户领取token的方法:
function claimTokens() external whenNotPaused {
// 校验当前时间在有效时间区间内
require(block.timestamp >= claimStartTime && block.timestamp < claimStartTime + claimDuration, "Claimable period is finished");
// 校验用户拥有有效nft
require((beta.balanceOf(msg.sender) > 0 || alpha.balanceOf(msg.sender) > 0), "Nothing to claim");
uint256 tokensToClaim;
uint256 gammaToBeClaim;
// 根据上面的方法,得到用户可以领取的数量
(tokensToClaim, gammaToBeClaim) = getClaimableTokenAmountAndGammaToClaim(msg.sender);
// 更新BAYC的领取数据并发送事件
for(uint256 i; i < alpha.balanceOf(msg.sender); ++i) {
uint256 tokenId = alpha.tokenOfOwnerByIndex(msg.sender, i);
if(!alphaClaimed[tokenId]) {
alphaClaimed[tokenId] = true;
emit AlphaClaimed(tokenId, msg.sender, block.timestamp);
}
}
// 更新MAYC的领取数据并发送事件
for(uint256 i; i < beta.balanceOf(msg.sender); ++i) {
uint256 tokenId = beta.tokenOfOwnerByIndex(msg.sender, i);
if(!betaClaimed[tokenId]) {
betaClaimed[tokenId] = true;
emit BetaClaimed(tokenId, msg.sender, block.timestamp);
}
}
// 更新Kennel Club的领取数据并发送事件
uint256 currentGammaClaimed;
for(uint256 i; i < gamma.balanceOf(msg.sender); ++i) {
uint256 tokenId = gamma.tokenOfOwnerByIndex(msg.sender, i);
// 注意这里是根据Kennel Club nft配对数量进行计算
if(!gammaClaimed[tokenId] && currentGammaClaimed < gammaToBeClaim) {
gammaClaimed[tokenId] = true;
emit GammaClaimed(tokenId, msg.sender, block.timestamp);
currentGammaClaimed++;
}
}
grapesToken.safeTransfer(msg.sender, tokensToClaim);
totalClaimed += tokensToClaim;
emit AirDrop(msg.sender, tokensToClaim, block.timestamp);
}
其他的方法例如,管理员转回未领取的token等,都比较简单,这里不再赘述。
APE 的空投合约相对来说比较简单,它有一个特点是根据用户的 NFT 实时持仓来进行空投,而不是在某一个区块或时间点进行快照。而这一点恰恰造成了套利的机会,有科学家根据这个特点进行闪电贷套利,获取了大量的 ETH。
No activity yet