Hello World - 2024年9月
type: channel
link: http://47.76.222.76/index.php/2024/09
description: Just So So ...
items: 流动性挖矿
items: Uniswap V2 详解
items: Uniswap V1 详解
items: 支持多链多币的跨链桥
items: 比特币 交易模型

流动性挖矿
type: item
date: 2024-09-29T01:50:00+00:00
description: 定义流动性挖矿是一种网络参与策略，用户向协议提供流动资金，以换取该协议的原生token或奖励。最原始的POW挖矿类似btc、eth等是用矿机去挖，这就需要大量机器、场地、技术团队等成本。后来有了POS挖矿，只要很少的机器和场地和技术团队成本，但是额外的为了防止女巫攻击，每个矿工都要质押币。他们挖的币来自哪里呢？一个是出块奖励，另一个是转账交易的手续费。但我们只有获得出块权才能获得奖励，这竞争还是很大的，所以一般人挖矿都是加入到大矿池，出块之后奖励大家按照比例分。对于类似Uniswap交易所的协议，都希望做大它的池子，所以需要我们向池子里添加流动性，它把交易的手续费作为奖励按比例分配给我们。DEX一般是智能合约，不需要出块，但也会发自己的代币，所以也会有一部分代币奖励给流动性提供者（简称LP）。这看起来与传统挖矿区别就不大了，质押虚拟币，然后按比例分交易手续费和平台代币，代币一般会越来越少，跟出块奖励越来越少一样。流动性挖矿项目根据设计和目的，可以分为三类：公平启动。主要目标是通过一些客观的标准（例如成为协议的活跃用户）来分配大部分的token，而不是直接销售，并确保每个人都有平等的机会来获得token。编程式去中心化：主要目标是逐步实现社区所有权并尽量减少资金管理。增长型营销：主要目标是在一段时间内激励特定的用户行为。每一个类别都有优点和缺点，某些协议的挖矿方案可能综合了多个类别。去中心化交易所去中心化交易所Uniswap就是流动性挖矿的典型应用，用户向Uniswap流动性提供流动资金然后获取手续费奖励。最早启动流动性挖矿的项目已经不好考证，但是让流动性挖矿一夜火爆起来的是Compound这个借贷项目。后来Sushi复制了Uniswap V2的代码，通过搞流动性挖矿，一度成功地把Uniswap的流动性给吸走了大部分。因为Compound的流动性挖矿是基于它的借贷模式，为了便于大家理解，我们用Sushi来解释流动性挖矿的原理，因为Sushi的代码和Uniswap是完全一样的，对于Swap，我们通过对Uniswap的AMM机制剖析，得出结论如下：一个交易池需要LP放入两种资产，才能实现AMM，让交易员实现Swap交易；一个LP放入两种资产到交易池后，他会得到相应数量的代币作为LP凭证，这个代币通常就称为LP，因为稍后可以用LP提取前期放入的流动性资产并获取手续费收入。注意到每个交易池对应的LP凭证代币都是不同的，不能用A池的LP代币去B池提取流动性。手续费是激励LP放入流动性的关键。手续费通常为0.1%～0.3%，其中大部分归LP，小部分由项目本身收取以支付开发运营费用等。Swap的手续费虽然看上去比传统的撮合交易要高几倍，但是链上交易频率较低，如果交易额度不是很大，手续费实际收入是很少的。越是交易额低的池子，手续费收入越低，做LP的人越少，反过来导致交易额越低。因此，如何激励LP，尤其是没有什么流动性的新交易池，就是一个大问题。我们来看看Sushi的流动性挖矿，是如何激励LP的。假设一个新的交易池ETH/USDC没有什么流动性，为了鼓励大家去做LP，Sushi引入了一种锁仓挖矿的方式：一个做市商放入ETH/USDC后，获得相应数量的LP；不要在手里持有LP，而是把LP质押到挖矿合约中，此合约按照设定的规则，按质押金额和质押时长发放奖励，奖励的代币就是SUSHI；一段时间后，从挖矿合约中解除质押，获得SUSHI奖励，再把解除质押的LP用于提取流动性资产，获得ETH/USDC以及手续费。由于存在挖矿，因此收益除了手续费，还额外多了挖矿奖励SUSHI。把SUSHI卖掉，那就是双份奖励，自然收获双份快乐。我们假设第一步投入总价值$10000的资产到交易池，获得10000个LP，再假定10000个LP放入挖矿合约中，每天收获100个SUSHI，一个SUSHI如果是$0.1，那么投入$10000，每天的挖矿收入就是$10，日收益率0.1%，年化收益率36.5%。但是，且慢，SUSHI也是凭空发出的代币，你挖我挖他也挖，挖出来你卖我卖他也卖，很快就能把SUSHI卖成$0.01，$0.001，直到归零。不下点套，能让你随随便便卖掉挖出来的SUSHI？怎么下套？这就是流动性挖矿的“精髓”所在。对ETH/USDC这种普通的交易池，每10000个LP每日可挖出100个SUSHI，这叫普通矿池。还有一种交易池，SUSHI/USDC，每10000个LP每日可挖出1000个SUSHI，这叫超级矿池。那谁会挖普通矿池啊，当然是直接挖超级矿池了！看仔细了，超级矿池是SUSHI/USDC，为了能挖超级矿池，首先要获得它的LP，而它的LP需要注入的资产是SUSHI和USDC。一开始挖矿的时候，大家手里有SUSHI么？为了挖超级矿池，你得先挖普通矿池，等待一段时间，…

Uniswap V2 详解
type: item
date: 2024-09-22T14:50:00+00:00
description: 一、新特性提供任意ERC20-ERC20交易对，不再局限于V1的ERC20-ETH交易对价格预言机，有效防止价格操纵，减缓价格波动闪电兑换（闪电贷），自由借出并使用代币，并在该交易的最后归还代币一个0.05%的协议手续费开关。如果开启，流动性提供者将获得0.25%手续费ERC20交易对Uniswap v1使用ETH作为桥梁货币，每个交易对都包含ETH作为其资产之一。这使得路由更简单，比如要想实现ABC和XYZ的交易，只需要分别使用ETH/ABC和ETH/XYZ交易对即可，减少了流动性的分散。然而，这条规则给流动性提供者带来了巨大的成本。所有流动性提供者都接触ETH，由于x * y = k引入的滑点，并根据其他资产相对于ETH的价格变化而遭受无常损失（类似股票的浮亏），简单而言就是在代币价格单方面（上涨或下跌）波动时，做市者手中持有的代币实际总价值反而减少。使用ETH作为强制的交易代币也会增加交易成本。相比直接使用ABC/XYZ交易对，他们将支付两倍的交易手续费，同时承受两倍的滑点。因为在v1，要想从ABC交易到XYZ，必须依次交易ABC/ETH和ETH/XYZ，因此手续费和滑点都需要两倍。v2允许流动性提供者为任意两个ERC-20代币创建交易对合约。但交易对数量的激增将给寻找最优交易路径带来困难，但是路由问题可以在上层解决（比如通过链下或链上的路由器或聚合器）v2不直接支持ETH的交易对，它需要包装成遵循ERC20标准的WETH Token。价格预言机Uniswap流动池在某个时间点的代币边际价格（不包含手续费），可以通过代币a和代币b的总量相除得出：p=a/b。当提供的价格不正确时，套利者可以在Uniswap交易套利，因此Uniswap提供的代币价格将跟随市场价格。这意味着Uniswap提供的代币价格可以作为一种近似的价格预言机。然而，Uniswap v1无法提供安全的链上预言机，因为它的价格很容易被操控。假设其他合约使用当前ETH-DAI价格作为衍生品交易的基准价格。攻击者可以从ETH-DAI交易对买入ETH来操控价格，并触发衍生品合约的清算，接着再将ETH卖回以使价格回归正常。上述操作可以通过一个原子交易完成，或者被矿工通过排序同一区块中的不同的交易来实现。注：由于V1的采样的价格是瞬时的，因此很容易通过买入卖出大额代币来操纵实时价格。Uniswap v2改进了预言机功能，通过在每个区块的第一笔交易前记录累计价格来实现，每个价格会以时间权重记录（基于当前区块与上一次更新价格的区块的时间差）。这意味着在任意时间点，该累计价格将是此合约历史上每秒的现货价格之和。操纵这个价格会比操纵区块中任意时间点的价格要困难。如果攻击者通过在区块的最后阶段提交一笔交易来操纵价格，其他套利者（发现价格差异后）可以在同一区块中提交另一笔交易来将价格恢复正常。矿工（或者支付了足够gas费用填充整个区块的攻击者）可以在区块的末尾操控价格，但是除非他们同时挖出了下一个区块，否则他们没有特殊的优势可以进行套利。注：由于价格预言机仅在每个区块记录一次，因此除非同一个人控制了两个区块的所有交易，否则他们将没有足够的套利优势。为了估算在t1到t2时间段内的时间加权平均价格（TWAP），外部调用者可以分别记录t1和t2的累计价格，将t2价格减去t1价格，并除以t2-t1的时间差。预言机的用户可以自行选择区间的开始和结束。选择一个更长的区间，意味着攻击者将花费更高的代价来操控该区间的时间加权平均价格，虽然这将导致该平均价格与实时价格相差较大。注：由于合约仅记录当前的累计价格，因此如果需要计算区间的平均价格，外部应用要自己记录并保存历史价格，合约本身不保存历史数据。Uniswap v2的TWAP计算方式实际上使用的是（加权）算数平均数。在数学上有一个毕达哥拉斯平均的概念，指的是三种经典平均数，分别是：算数平均数、几何平均数和调和平均数。其中，算术平均数是最常见的一种平均数，其优点是计算简单，缺点是容易受到极端数据的影响，导致均值误差；几何平均数相比算术平均数，更适用于在金融市场场景，因为金融市场价格本身是一种布朗运动；调和平均数更易受到极小值的影响，一般应用于计算平均速率等场景。从应用场景上，Uniswap价格均值应该使用几何平均数更合适，均值的误差更小，但由于几何平均数在以太坊合约上实现难度较大，所以Uniswap v2版本采用算数平均数；但是Uniswap v3则使用几何平均数计算价格预言机。一个难题：我们应该计算以B代币计价的A代币价格，还是以A代币计价的B代币价格？虽然在现货价格上，以B代币计价的A代币价格（B/A）与以A代币计价的B代币价格（A/B）总是互为倒数，但在计算某个时间区间的算数平均数时，二者却不是互为倒数关系。比如，假设在区块1的价格为100…

Uniswap V1 详解
type: item
date: 2024-09-21T11:51:00+00:00
description: Uniswap V1本质是一个去中心化的自动交易所，提供ETH和任意ERC20代币之间相互兑换的功能。特点是去中心化、去监管化和安全性，是一个开源的公共产品，不收取交易费用。概览Uniswap采用恒定乘积自动做市系统，即资金池内的ETH和ERC20代币数量的乘积总体是恒定的。与传统交易所基于订单簿并促进买卖双方的代币交易方式不同，Uniswap是基于各种代币的流动性储备来完成代币兑换交易的，这样的好处是买卖双方能快速直接地完成代币兑换而不必挂单等待。代币兑换的价格由恒定乘积做市场来决定（x*y=k），这种自动做市的机制能使得代币的总体储备相对均衡。代币的储备由流动性提供者保障，流动性提供者会按代币交易费用的一定比例获取奖励。Uniswap的一个重要特征是利用工厂/注册（factory/registry）合约，为每个ERC20代币部署一个单独的代币兑换合约，并且这些交易合约各自持有ETH及其相关的ERC20代币来用于代币兑换。 代币兑换合约通过注册表链接，因此可以以ETH作为中介直接进行ERC20代币到ERC20代币的兑换交易。Uniswap V1的体系相对来说比较简单，分为两个合约：Exchange，用于进行ETH和ERC-20代币之间的兑换。Factory，用于创建和记录所有的Exchange，也用于查询代币对应的Exchange。Factory合约contract Exchange(): def setup(token_addr: address): modifying NewExchange: event({token: indexed(address), exchange: indexed(address)}) exchangeTemplate: public(address) tokenCount: public(uint256) token_to_exchange: address[address] exchange_to_token: address[address] id_to_token: address[uint256] @public def initializeFactory(template: address): assert self.exchangeTemplate == ZERO_ADDRESS assert template != ZERO_ADDRESS self.exchangeTemplate = template @public def createExchange(token: address) -> address: assert token != ZERO_ADDRESS assert self.exchangeTemplate != ZERO_ADDRESS assert self.token_to_exchange[token] == ZERO_ADDRESS exchange: address = create_with_code_of(self.exchangeTemplate) Exchange(exchange).setup(token) self.token_to_exchange[token] = exchange self.exchange_to_token[exchange] = token token_id: uint256 = self.tokenCount + 1 self.tokenCount = token_id self.id_to_token[token_id] = token log.NewExchange(token, exchange) return exchange @public @constant def getExchange(token: address) -> address: return self.token_to_exchange[token] @public @constant def getToken(exchange: address) -> address: return self.exchange_to_token[exchange] @public @constant def getTokenWithId(token_id: uint256) -> address: return self.id_to_token[token_id]initializeFactory只在创建的时候被调用，一旦设置了template参数后就无法更改，确保了用于创建Exchange的代码模板不会被修改。template是链上部署的合约，用于作为后续创建的Exchange的模板。createExchange用于从模板创建一个Excha…

支持多链多币的跨链桥
type: item
date: 2024-09-15T15:06:00+00:00
description: 需求分析可以支持多条链，只要链支持Solidity智能合约即可可以支持多个代币，需要符合ERC20标准的代币由去中心化的中继节点采用投票方式来完成跨链请求的确认每个中继节点需要质押保证金，每成功一笔跨链交易可获取奖励引入惩罚机制，中继节点每次作恶都会被罚没，保证金低于一定值时被封禁，重新充入保证金可解封引入黑名单管理机制，符合监管需求，禁止名单内账户使用跨链桥功能引入中继节点准入机制，维持中继节点集群稳定，保障跨链桥的稳定工作跨链交易规则跨链有最低金额限制（比如100USDT）每笔收取总金额的一定比例作为手续费（比如万分之五）一定比例的手续费平分给参与投票的中继节点（比如70%）剩下比例的手续费发给平台（比如30%）用户需手动去领取跨链资产，也可以在指定一个帮忙自动提取的中继节点（需额外付费）中继节点规则中继节点列表由平台进行管理维护，需保持稳定（避免在短时间内大量增加或减少）保证金由中继节点自行维护，存入自由，取出需要限制（比如每隔24小时可取一次）中继节点数量需保持一定合适的数量，至少也得3个，太多中继节点也不好（浪费机器资源，也拖慢投票速度）投票罚没规则中继节点需保证金大于指定门槛的才可以参与投票（比如10万），低于的处于封禁状态中继节点的投票权重都一样，和保证金大小无关每次跨链请求需要大于2/3的中继节点（所有的包括封禁的）投票才算通过投票时需要附带中继节点的签名（合约可以验证签名来核查投票结果并允许用户提取对应资产）每次作恶罚没一定比例的保证金（比如5万），罚没的保证金转给平台地址如果某个节点持续作恶，会不断损失保证金，平台也可以将其从中继节点列表中移除跨链通道要进行跨链交易，需要在两条链之间创建一个通道通道是有方向的，一般情况要添加双向的：A链->B链和B链->A链通道需指定两条链关联的代币地址相同的两条链可添加多对代币地址参与角色普通用户-web前端：发起跨链请求、领取跨链资产、查看交易记录（历史的和进行中的）中继节点-后台服务：跨链交易投票、帮用户自动领取跨链资产中继节点-web前端：查看管理保证金、查看收益、查看投票记录平台管理-web前端：查看管理中继节点、查看保证金、查看收益列表、查看修改黑名单、查看交易记录数据分析与监控：交易汇总分析、中继节点收益分析、资金流动分析、跨链桥异常预警合约设计核心流程：用户在A链存入（冻结）资产、中继节点投票（签名）确认、使用超2/3的签名在B链取出（解冻）资产引入一个廉价的链来存储投票过程，而不是中继节点自行共识，投票过程更安全，也可追溯，提高系统的可用性、安全性用来投票的链这里称为桥链（bridge chain），它还可用来存储一些其它数据或运行一些服务使用签名技术减少合约调用次数，简化设计与节省交易gas费为了节省交易gas费，所有投票在桥链上完成，其它功能也尽量在桥链上实现投票就是签名，用户投票则是将签名写入到桥链合约中跨链通道定义：通道id=keccak256(来源链id,来源链代币地址,目标链id,目标链代币地址)跨链交易定义：交易id=keccak256(通道id,时间戳,接收者,金额,手续费)跨链交易记为KR，投票就是对KR进行签名，记为：KS，有效投票需满足数据无篡改和签名可验证架构桥核心合约（资产）：跨链资金出入口、中继成员列表、黑名单列表桥治理合约（投票）：签名存储与验证、保证金管理、惩罚机制、手续费分发桥链部署：治理合约A链部署：资产合约B链部署：资产合约C链部署：资产合约桥核心合约接口-添加中继节点接口-移除中继节点接口-将账户地址加入黑名单接口-将账户地址从黑名单删除接口-添加跨链通道接口-移除跨链通道接口-跨链转入资产（用户资产充足且未在黑名单内）接口-跨链取出资产（需通过2/3的中继节点签名验证）事件-中继节点变更、黑名单变更、资产转入、资产转出、资产解冻、交易取消查询-中继节点、黑名单、跨链USDT额度（到不同链上的额度）变量-跨链USDT额度列表、中继节点列表、黑名单列表桥治理合约接口-增加保证金接口-减少保证金接口-上传签名（验证签名，作恶罚没）事件-保证金变更、签名提交、投票结果、罚没查询-保证金、签名、罚没次数、罚没金额变量-保证金余额列表、签名列表、跨链交易列表、惩罚记录表合约执行以以太坊和波场为例，以稳定币USDT为例初始化两条链的桥核心合约设置相同的10个中继节点地址和黑名单列表在桥链的桥治理合约中为10个中继节点地址冲入20万保证金平台往两条链的桥核心合约中存入10万USDT作为流动资金Ethereum >>> Tron 跨链用户调用Ethereum链资产合约的跨链转入接口检查用户不在黑名单内，黑名单用户交易直接失败将用户的1万个USDT转到资产合约地址名下更新变量：到Tron链的跨链USDT额度减少1万发出事件：跨链转入（…

比特币 交易模型
type: item
date: 2024-09-01T14:28:00+00:00
description: 与以太坊的账户模型不同，比特币是没有余额概念的，使用的是UTXO模型。所以在比特币网络中，存储比特币的是交易输出，准确点说就是未使用过的交易输出。UTXOUTXO是Unspent Transaction Outputs的缩写（Transaction可简写为TX），即未使用过的交易输出。比特币节点会追踪UTXO集合，从而确定哪些代币未被花费，以及哪些人可以花费它们。最初的比特币是通过铸币交易创造的，它是用来奖励矿工的一种特殊交易，因为是凭空创造的，所以它没有输入的。新的UTXO是通过花费现有UTXO创建的，UTXO包含了接收地址和金额。每个比特币交易都由输入和输出组成。输入花费已有UTXO，输出则创建新的UTXO，UTXO只要被用一次，就不存在“未花费”了，以后就不会再被引用，这样就解决双花（双重支付）问题。当我们要使用一个UTXO时，需要对UTXO进行解锁（一般是用私钥对UTXO进行签名），虽然UTXO是公开的，但是如果没有私钥去解锁，则不能花费UTXO。交易结构交易是比特币的核心数据结构，包括区块在内的数据结构都是在为交易服务。交易主要由未花费的输入和输出组成，输出总金额不能大于输入的总金额，多余的部分为交易矿工费有三种类型交易铸币交易（Generation TX），有额外的Coinbase字段，矿工可以随意填写一些信息，因为是挖矿所得，所以这种交易类型没有输入，输出数量就是矿工所得，地址是矿工的地址。普通交易（Pubkey Hash TX），转账交易，一个或多个输入，一个或两个输出，第一个输出称为支付，第二个输出称为找零脚本交易（Script Hash TX），一般为多签脚本，为普通交易升级版本，可以生成灵活的锁定脚本，然后使用特定的解锁脚本去花费比特币字段表数据项大小(Byte)数据类型描述nVersion4int32_t交易版本vin不定CTxIn列表交易输入vout不定CTxOut列表交易输出nLockTime4uint32_t交易锁定时间交易输入CTxIn数据项大小(Byte)数据类型描述prevout不定COutPoint上一个交易的输出，由交易哈希和索引值确定scriptSig不定char[]解锁脚本nSequence4uint32_t序列号，可用于相对时间锁定交易输出CTxOut数据项大小(Byte)数据类型描述nValue8int64_t交易输出金额，单位为SatoshisscriptPubKey不定char[]锁定脚本，定义花费须满足的条件创币交易CoinbaseTransaction每一个区块内包含的第一条交易为CoinbaseTransaction，它作为对挖出该区块的矿工的比特币奖励交易。没有交易输入交易哈希为0输出的索引值固定，为0xffffffffBIP34规定增加一个4字节的height的字段，这个参数是必须的源码参考代码路径：https://github.com/bitcoin/bitcoin/blob/master/src/primitives/transaction.hCOutPut/** An outpoint - a combination of a transaction hash and an index n into its vout * ** 一个交易哈希值与输出下标的集合 */ class COutPoint { public: uint256 hash; //交易哈西 uint32_t n; //对应序列号 COutPoint(): n((uint32_t) -1) { } COutPoint(const uint256& hashIn, uint32_t nIn): hash(hashIn), n(nIn) { } ADD_SERIALIZE_METHODS; //用来序列化数据结构，方便存储和传输 template <typename Stream, typename Operation> inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(hash); READWRITE(n); } void SetNull() { hash.SetNull(); n = (uint32_t) -1; } bool IsNull() const { return (hash.IsNull() && n == (uint32_t) -1); } //小于号<重载函数 friend bool operator<(const COutPoint& a, const COutPoint& b) { int cmp = a.hash.Compare(b.hash); return cmp < 0 |…