
DeFi概念介绍(三)——无常损失和滑点
上篇文章我们讲了AMM的原理,并在最后提到了无常损失这个概念。今天这篇文章就来详细介绍无常损失和滑点这两个概念。无常损失当你成为一个做交易流动池添加者(又称做市商、矿工)时,相当于是和所有交易者做一个对手盘,如果所有交易者的盈亏加在一起还是盈利状态,那这部分的利润就来自于每个做市商的无常损失。 同时无常损失不是永久性的,价格经历短暂下跌后又重新恢复,收益也会修正。 那么如何计算无常损失呢? 还是先列出AMM的核心公式:K = A * B 假设以BNB和BUSD为例:Cbnb - 池中当前BNB 的数量 为上式中的ACbusd - 池中当前BUSD 的数量 为上式中的BPbnb - bnb当前价格Pbusd - busd当前价格 恒定为1u 可省略当K恒定时,我们可以得到任何时刻池中的BNB和BUSD数量真实场景假如,我们在 1 BNB = 500 BUSD 的时候,组了一组 LP 。我们拿出了 20 个 BNB 和 10000 个 BUSD 进行 LP 流动性提供代币兑换。此时我们得到了这几个变量:并且,我们保证此时的 K 也是后续所有情况下的常数 K,即组完 LP 代币后即时生...

Gitcoin女巫检测方案Top1解读及思考
GITCOIN在两个月前举办了 OpenData 社区黑客马拉松!其中公布了三个重点领域,分别是: 女巫检测、捐款激励措施优化分析、Dune高效分析,其结果也于近日公布。 相信大家最关心的应该是女巫问题,因为今年在OP和APT的刺激下,出现了人人羡慕撸毛党,人人皆是撸毛党的盛况。 今天对女巫检测的第一名开源方案结合自身理解,进行技术向的解读,并在文末给出自己对于女巫检测的一些个人思考。相信看完这篇文章,无论是项目方还是交互者都能有所收获。 注:本文不代表官方观点,仅为个人兴趣解读。 剧透,本文较长,涉及很多技术分析,没耐心的可以直接跳转文末浏览本文总结与个人思考。由于出题者是gitcoin,其主要交互场景为捐赠。但内在逻辑在其他的场景下同样适用(transfer、mint等动作),以下将与项目方进行交互的操作统称为项目交互。方案一:批量转移和交互女巫攻击本质上是用户将资金分散到多个地址,操纵这些地址与项目方合约进行交互的过程。 那么在这个过程中则可以将整个过程拆分两个部分,分别是 批量资金转移和合约批量交互。1.1 批量转账检测选择数据批量转账最简单的就是通过智能合约的方式进行,...
零知识证明和Layer2简介
1、零知识证明简单介绍零知识证明是一方(证明者)向另一方(检验者)在不透露具体内容的条件下证明某命题的方法。 举例: 两个富翁A和B相遇,两人的资产都在1-10亿之间,要如何在不告诉对方自己具体财富的情况下,得出对方是否比自己有钱?区块链中的零知识证明:其实大家可能会疑惑,为什么会用到零知识证明,那其实。在区块链机制中,想要去证明自己有存储某个东西的时候。他其实是不会把这个东西全部发给你来证明?比如说存储了一部电影我,可能不会把整个电影发给你了他,会有一种证明机制去,证明里面的某一块或者是某个东西他是有的,通过提交某个证明去给到系统,系统知道你有存这个东西这种就是其实就是零知识证明的一种,就是不公布具体内容,但是证明某个事情。例如银行贷款必须提交资产证明,通过零知识,无需提供银行房本,住址这些资料。2、Layer2简单介绍起源Layer2的诞生是为了解决以太坊主网拥堵及昂贵的问题,在Eth2.0完成之前保持住以太坊上生态霸主的地位。 广泛层面上,Layer2包含所有和以太坊主链有桥接的项目,包含像Polygon这类几乎独立的区块链; 狭义层面,指的是指以以太坊主网作为最终状态记录...

DeFi概念介绍(三)——无常损失和滑点
上篇文章我们讲了AMM的原理,并在最后提到了无常损失这个概念。今天这篇文章就来详细介绍无常损失和滑点这两个概念。无常损失当你成为一个做交易流动池添加者(又称做市商、矿工)时,相当于是和所有交易者做一个对手盘,如果所有交易者的盈亏加在一起还是盈利状态,那这部分的利润就来自于每个做市商的无常损失。 同时无常损失不是永久性的,价格经历短暂下跌后又重新恢复,收益也会修正。 那么如何计算无常损失呢? 还是先列出AMM的核心公式:K = A * B 假设以BNB和BUSD为例:Cbnb - 池中当前BNB 的数量 为上式中的ACbusd - 池中当前BUSD 的数量 为上式中的BPbnb - bnb当前价格Pbusd - busd当前价格 恒定为1u 可省略当K恒定时,我们可以得到任何时刻池中的BNB和BUSD数量真实场景假如,我们在 1 BNB = 500 BUSD 的时候,组了一组 LP 。我们拿出了 20 个 BNB 和 10000 个 BUSD 进行 LP 流动性提供代币兑换。此时我们得到了这几个变量:并且,我们保证此时的 K 也是后续所有情况下的常数 K,即组完 LP 代币后即时生...

Gitcoin女巫检测方案Top1解读及思考
GITCOIN在两个月前举办了 OpenData 社区黑客马拉松!其中公布了三个重点领域,分别是: 女巫检测、捐款激励措施优化分析、Dune高效分析,其结果也于近日公布。 相信大家最关心的应该是女巫问题,因为今年在OP和APT的刺激下,出现了人人羡慕撸毛党,人人皆是撸毛党的盛况。 今天对女巫检测的第一名开源方案结合自身理解,进行技术向的解读,并在文末给出自己对于女巫检测的一些个人思考。相信看完这篇文章,无论是项目方还是交互者都能有所收获。 注:本文不代表官方观点,仅为个人兴趣解读。 剧透,本文较长,涉及很多技术分析,没耐心的可以直接跳转文末浏览本文总结与个人思考。由于出题者是gitcoin,其主要交互场景为捐赠。但内在逻辑在其他的场景下同样适用(transfer、mint等动作),以下将与项目方进行交互的操作统称为项目交互。方案一:批量转移和交互女巫攻击本质上是用户将资金分散到多个地址,操纵这些地址与项目方合约进行交互的过程。 那么在这个过程中则可以将整个过程拆分两个部分,分别是 批量资金转移和合约批量交互。1.1 批量转账检测选择数据批量转账最简单的就是通过智能合约的方式进行,...
零知识证明和Layer2简介
1、零知识证明简单介绍零知识证明是一方(证明者)向另一方(检验者)在不透露具体内容的条件下证明某命题的方法。 举例: 两个富翁A和B相遇,两人的资产都在1-10亿之间,要如何在不告诉对方自己具体财富的情况下,得出对方是否比自己有钱?区块链中的零知识证明:其实大家可能会疑惑,为什么会用到零知识证明,那其实。在区块链机制中,想要去证明自己有存储某个东西的时候。他其实是不会把这个东西全部发给你来证明?比如说存储了一部电影我,可能不会把整个电影发给你了他,会有一种证明机制去,证明里面的某一块或者是某个东西他是有的,通过提交某个证明去给到系统,系统知道你有存这个东西这种就是其实就是零知识证明的一种,就是不公布具体内容,但是证明某个事情。例如银行贷款必须提交资产证明,通过零知识,无需提供银行房本,住址这些资料。2、Layer2简单介绍起源Layer2的诞生是为了解决以太坊主网拥堵及昂贵的问题,在Eth2.0完成之前保持住以太坊上生态霸主的地位。 广泛层面上,Layer2包含所有和以太坊主链有桥接的项目,包含像Polygon这类几乎独立的区块链; 狭义层面,指的是指以以太坊主网作为最终状态记录...
Share Dialog
Share Dialog
<100 subscribers
<100 subscribers
这篇文章从合约代买的角度带大家了解一下 去年土狗风靡一时的彩票模型和自动流动性添加模型。
核心每次交易的部分手续费进入彩票池,当彩票池中金额达到开奖标准,则随机在持有者中抽取一个钱包地址,转入奖金,当然,持有者必须符合持有一定代币数量,否则将本期奖金打入开发者钱包。
找了个之前买的meme币 catgirl 0x79eBC9A2ce02277A4b5b3A768b1C0A4ed75Bd936 来解读一下彩票的代码:
mapping (address => uint256) private _rOwned; // address=> 持币数量 映射
address[] private _addressList; // 存所有地址
address payable private _devWallet; // 开发者钱包
address private _lottoPotAddress; //抽奖地址
address private _lottoWalletAddress; // 抽奖钱包地址
uint256 public _lastLottoWinnerAmount; // 最后赢家奖金金额
uint256 public _totalLottoPrize; // 总奖金金额
uint public _lottoDrawCount = 0; // 计数
uint256 public lotteryThreshold = 10 * 10**12 * 10**9; // 开奖阈值
bool inSwapAndLiquify; // 是否已经进入流动性添加环节标志 默认为false
bool inLotteryDraw; // 是否已经进入开奖环节标志 默认为false
bool public swapAndLiquifyEnabled = true; // 是否开启 自动加入流动性方法 标志
bool public lottoEnabled = true; // 是否开启 彩票抽奖方法 标志
bool public _shouldSwapToBNB = false;
合约 _transfer()方法中出现
function _transfer(
address from,
address to,
uint256 amount
) private {
.......
// 查看 彩票奖池里的代币余额
uint256 lottoBalance = balanceOf(_lottoPotAddress);
// 彩票奖池中余额是否大于开奖阈值
bool overMinLottoBalance = lottoBalance >= lotteryThreshold;
if (
overMinLottoBalance && !inSwapAndLiquify && !inLotteryDraw && lottoEnabled
) {
drawLotto(lottoBalance);
}
......
}
开奖程序是否启动设置函数
function setLottoEnabled(bool enabled) public onlyOwner() {
lottoEnabled = enabled;
}
彩票抽奖方法主方法:
event DrawLotto(uint256 amount, uint _lottoDrawCount);
// 函数修改器 修改进入流程标志
modifier lockTheLottery {
inLotteryDraw = true;
_;
inLotteryDraw = false;
}
function lotterize() private view returns(address) {
uint256 randomNumber = random().mod(_addressList.length);
uint256 ownedAmount = _rOwned[_addressList[randomNumber]];
// 如果随机的address 持有代币数量不足 直接打入开发者钱包
if (ownedAmount >= _minLottoBalance) {
return _addressList[randomNumber];
}
return _devWallet;
}
function drawLotto(uint256 amount) private lockTheLottery {
_lottoWalletAddress = lotterize();
// 此时将amount 转入 中奖者 地址
// 并且由于函数修改器 将inLotteryDraw = true 故再次转账时跳过抽奖环节
_transfer(_lottoPotAddress, _lottoWalletAddress, amount);
_lastLottoWinnerAmount = amount;
// 总发出奖金
_totalLottoPrize = _totalLottoPrize.add(amount);
// 总开奖次数
++_lottoDrawCount;
// 链上上报事件
emit DrawLotto(amount, _lottoDrawCount);
}
流动性自动添加模型就是在每笔交易中收取一部分手续费,将其累计,当到达一定的金额,自动拿出积累的一半token换入 eth 组成[eth, token]交易对,注入流动池。这样会保证池子一直充盈,不会被大户交易吸干。
如果看懂了上面的彩票模型,那么流动性自动添加也是一个套路
event SwapAndLiquify(
uint256 tokensSwapped,
uint256 ethReceived,
uint256 tokensIntoLiqudity
);
function setSwapAndLiquifyEnabled(bool _enabled) public onlyOwner {
swapAndLiquifyEnabled = _enabled;
emit SwapAndLiquifyEnabledUpdated(_enabled);
}
每次调用_transfer()方法,进行一次流动性添加判断
function _transfer(
address from,
address to,
uint256 amount
) private {
....
// 查看 合约地址内的代币余额 每笔交易的手续费中一部分存在address(this) 中作为流动性储备
uint256 contractTokenBalance = balanceOf(address(this));
// 查看 是否触发最大转账金额 默认_maxTxAmount 为代币对外总数量 10000 * 10**12 *10*9
if(contractTokenBalance >= _maxTxAmount)
{
contractTokenBalance = _maxTxAmount;
}
// 持有数量是否 超过转移数量就加入流动性阈值
// numTokensSellToAddToLiquidity 初始为 50 * 10**12 *10**9
bool overMinTokenBalance = contractTokenBalance >= numTokensSellToAddToLiquidity;
if (
overMinTokenBalance && !inSwapAndLiquify && from != uniswapV2Pair && swapAndLiquifyEnabled
) {
contractTokenBalance = numTokensSellToAddToLiquidity;
//add liquidity
// 流动性添加
swapAndLiquify(contractTokenBalance);
}
....
}
进入swapAndLiquify() 方法
// 函数修改器 修改 流动性添加是否在进行的标志
modifier lockTheSwap {
inSwapAndLiquify = true;
_;
inSwapAndLiquify = false;
}
function swapAndLiquify(uint256 contractTokenBalance) private lockTheSwap {
// split the contract balance into halves
// contractTokenBalance 的一半换成 eth
uint256 half = contractTokenBalance.div(2);
uint256 otherHalf = contractTokenBalance.sub(half);
// capture the contract's current ETH balance.
// this is so that we can capture exactly the amount of ETH that the
// swap creates, and not make the liquidity event include any ETH that
// has been manually sent to the contract
// 查看初始时 address(this)中的eth数量
uint256 initialBalance = address(this).balance;
// swap tokens for ETH
// 换eth操作
swapTokensForEth(half); // <- this breaks the ETH -> HATE swap when swap+liquify is triggered
// how much ETH did we just swap into?
// 换到了多少eth
uint256 newBalance = address(this).balance.sub(initialBalance);
// add liquidity to uniswap
// 一半contractTokenBalance 和 一半contractTokenBalance换的eth 添加进流动性池
addLiquidity(otherHalf, newBalance);
// 报链上事件
emit SwapAndLiquify(half, newBalance, otherHalf);
}
function swapTokensForEth(uint256 tokenAmount) private {
// generate the uniswap pair path of token -> weth
address[] memory path = new addressUnsupported embed;
path[0] = address(this);
path[1] = uniswapV2Router.WETH();
_approve(address(this), address(uniswapV2Router), tokenAmount);
// make the swap
uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
tokenAmount,
0, // accept any amount of ETH
path,
address(this),
block.timestamp
);
}
function addLiquidity(uint256 tokenAmount, uint256 ethAmount) private {
// approve token transfer to cover all possible scenarios
_approve(address(this), address(uniswapV2Router), tokenAmount);
// add the liquidity
uniswapV2Router.addLiquidityETH{value: ethAmount}(
address(this),
tokenAmount,
0, // slippage is unavoidable
0, // slippage is unavoidable
owner(),
block.timestamp
);
}
这篇文章从合约代买的角度带大家了解一下 去年土狗风靡一时的彩票模型和自动流动性添加模型。
核心每次交易的部分手续费进入彩票池,当彩票池中金额达到开奖标准,则随机在持有者中抽取一个钱包地址,转入奖金,当然,持有者必须符合持有一定代币数量,否则将本期奖金打入开发者钱包。
找了个之前买的meme币 catgirl 0x79eBC9A2ce02277A4b5b3A768b1C0A4ed75Bd936 来解读一下彩票的代码:
mapping (address => uint256) private _rOwned; // address=> 持币数量 映射
address[] private _addressList; // 存所有地址
address payable private _devWallet; // 开发者钱包
address private _lottoPotAddress; //抽奖地址
address private _lottoWalletAddress; // 抽奖钱包地址
uint256 public _lastLottoWinnerAmount; // 最后赢家奖金金额
uint256 public _totalLottoPrize; // 总奖金金额
uint public _lottoDrawCount = 0; // 计数
uint256 public lotteryThreshold = 10 * 10**12 * 10**9; // 开奖阈值
bool inSwapAndLiquify; // 是否已经进入流动性添加环节标志 默认为false
bool inLotteryDraw; // 是否已经进入开奖环节标志 默认为false
bool public swapAndLiquifyEnabled = true; // 是否开启 自动加入流动性方法 标志
bool public lottoEnabled = true; // 是否开启 彩票抽奖方法 标志
bool public _shouldSwapToBNB = false;
合约 _transfer()方法中出现
function _transfer(
address from,
address to,
uint256 amount
) private {
.......
// 查看 彩票奖池里的代币余额
uint256 lottoBalance = balanceOf(_lottoPotAddress);
// 彩票奖池中余额是否大于开奖阈值
bool overMinLottoBalance = lottoBalance >= lotteryThreshold;
if (
overMinLottoBalance && !inSwapAndLiquify && !inLotteryDraw && lottoEnabled
) {
drawLotto(lottoBalance);
}
......
}
开奖程序是否启动设置函数
function setLottoEnabled(bool enabled) public onlyOwner() {
lottoEnabled = enabled;
}
彩票抽奖方法主方法:
event DrawLotto(uint256 amount, uint _lottoDrawCount);
// 函数修改器 修改进入流程标志
modifier lockTheLottery {
inLotteryDraw = true;
_;
inLotteryDraw = false;
}
function lotterize() private view returns(address) {
uint256 randomNumber = random().mod(_addressList.length);
uint256 ownedAmount = _rOwned[_addressList[randomNumber]];
// 如果随机的address 持有代币数量不足 直接打入开发者钱包
if (ownedAmount >= _minLottoBalance) {
return _addressList[randomNumber];
}
return _devWallet;
}
function drawLotto(uint256 amount) private lockTheLottery {
_lottoWalletAddress = lotterize();
// 此时将amount 转入 中奖者 地址
// 并且由于函数修改器 将inLotteryDraw = true 故再次转账时跳过抽奖环节
_transfer(_lottoPotAddress, _lottoWalletAddress, amount);
_lastLottoWinnerAmount = amount;
// 总发出奖金
_totalLottoPrize = _totalLottoPrize.add(amount);
// 总开奖次数
++_lottoDrawCount;
// 链上上报事件
emit DrawLotto(amount, _lottoDrawCount);
}
流动性自动添加模型就是在每笔交易中收取一部分手续费,将其累计,当到达一定的金额,自动拿出积累的一半token换入 eth 组成[eth, token]交易对,注入流动池。这样会保证池子一直充盈,不会被大户交易吸干。
如果看懂了上面的彩票模型,那么流动性自动添加也是一个套路
event SwapAndLiquify(
uint256 tokensSwapped,
uint256 ethReceived,
uint256 tokensIntoLiqudity
);
function setSwapAndLiquifyEnabled(bool _enabled) public onlyOwner {
swapAndLiquifyEnabled = _enabled;
emit SwapAndLiquifyEnabledUpdated(_enabled);
}
每次调用_transfer()方法,进行一次流动性添加判断
function _transfer(
address from,
address to,
uint256 amount
) private {
....
// 查看 合约地址内的代币余额 每笔交易的手续费中一部分存在address(this) 中作为流动性储备
uint256 contractTokenBalance = balanceOf(address(this));
// 查看 是否触发最大转账金额 默认_maxTxAmount 为代币对外总数量 10000 * 10**12 *10*9
if(contractTokenBalance >= _maxTxAmount)
{
contractTokenBalance = _maxTxAmount;
}
// 持有数量是否 超过转移数量就加入流动性阈值
// numTokensSellToAddToLiquidity 初始为 50 * 10**12 *10**9
bool overMinTokenBalance = contractTokenBalance >= numTokensSellToAddToLiquidity;
if (
overMinTokenBalance && !inSwapAndLiquify && from != uniswapV2Pair && swapAndLiquifyEnabled
) {
contractTokenBalance = numTokensSellToAddToLiquidity;
//add liquidity
// 流动性添加
swapAndLiquify(contractTokenBalance);
}
....
}
进入swapAndLiquify() 方法
// 函数修改器 修改 流动性添加是否在进行的标志
modifier lockTheSwap {
inSwapAndLiquify = true;
_;
inSwapAndLiquify = false;
}
function swapAndLiquify(uint256 contractTokenBalance) private lockTheSwap {
// split the contract balance into halves
// contractTokenBalance 的一半换成 eth
uint256 half = contractTokenBalance.div(2);
uint256 otherHalf = contractTokenBalance.sub(half);
// capture the contract's current ETH balance.
// this is so that we can capture exactly the amount of ETH that the
// swap creates, and not make the liquidity event include any ETH that
// has been manually sent to the contract
// 查看初始时 address(this)中的eth数量
uint256 initialBalance = address(this).balance;
// swap tokens for ETH
// 换eth操作
swapTokensForEth(half); // <- this breaks the ETH -> HATE swap when swap+liquify is triggered
// how much ETH did we just swap into?
// 换到了多少eth
uint256 newBalance = address(this).balance.sub(initialBalance);
// add liquidity to uniswap
// 一半contractTokenBalance 和 一半contractTokenBalance换的eth 添加进流动性池
addLiquidity(otherHalf, newBalance);
// 报链上事件
emit SwapAndLiquify(half, newBalance, otherHalf);
}
function swapTokensForEth(uint256 tokenAmount) private {
// generate the uniswap pair path of token -> weth
address[] memory path = new addressUnsupported embed;
path[0] = address(this);
path[1] = uniswapV2Router.WETH();
_approve(address(this), address(uniswapV2Router), tokenAmount);
// make the swap
uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
tokenAmount,
0, // accept any amount of ETH
path,
address(this),
block.timestamp
);
}
function addLiquidity(uint256 tokenAmount, uint256 ethAmount) private {
// approve token transfer to cover all possible scenarios
_approve(address(this), address(uniswapV2Router), tokenAmount);
// add the liquidity
uniswapV2Router.addLiquidityETH{value: ethAmount}(
address(this),
tokenAmount,
0, // slippage is unavoidable
0, // slippage is unavoidable
owner(),
block.timestamp
);
}
No comments yet