




<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>ripwu&#039;s blog</title>
	<atom:link href="https://godorz.info/feed/" rel="self" type="application/rss+xml" />
	<link>https://godorz.info</link>
	<description>海明威说：这个世界很美好，我们应该为之奋斗。我同意后半句</description>
	<lastBuildDate>Sun, 02 Feb 2025 02:54:47 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.5.8</generator>
	<item>
		<title>2024 年小结</title>
		<link>https://godorz.info/2025/01/2024/</link>
					<comments>https://godorz.info/2025/01/2024/#respond</comments>
		
		<dc:creator><![CDATA[ripwu]]></dc:creator>
		<pubDate>Sun, 05 Jan 2025 05:41:46 +0000</pubDate>
				<category><![CDATA[My Thoughts]]></category>
		<guid isPermaLink="false">https://godorz.info/?p=2160</guid>

					<description><![CDATA[今年读了一些书，多是无聊时的消遣。 有几本严肃著作，由于见识和基础不够，浅尝辄止，希望明年可以读完。 另外准备看看经典影视作品。 小岛经济学 [美] 彼得·希夫 安德鲁·希夫 经济学的思维方式 [美] 保罗·海恩 彼得·勃特克 大卫·普雷契特科 杀死一只知更鸟 [美] 哈珀·李 斯通纳 [美] 约翰·威廉斯 法治的细节 罗翔 一个叫欧维的男人决定去死 [瑞典] 弗雷德里克·巴克曼 珍珠 [美] 约翰·斯坦贝克 产业与文明 张笑宇 小红马 [美] 约翰·斯坦贝克 好的心理治疗只需一次 涂道坤 煎饼坪 [美] 约翰·斯坦贝克 月亮下去了 [美] 约翰·斯坦贝克 烦恼的冬天 [美] 约翰·斯坦贝克 食南之徒 马伯庸 江南困局 唐元鹏 他影响了中国：陈云 叶永烈 历史转折中的邓小平 龙平平 中国官僚政治研究 王亚南]]></description>
										<content:encoded><![CDATA[<p>今年读了一些书，多是无聊时的消遣。</p>
<p>有几本严肃著作，由于见识和基础不够，浅尝辄止，希望明年可以读完。</p>
<p>另外准备看看经典影视作品。</p>
<table>
<thead>
<tr>
<th style="text-align: left;"></th>
<th style="text-align: left;"></th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">小岛经济学</td>
<td style="text-align: left;">[美] 彼得·希夫 安德鲁·希夫</td>
</tr>
<tr>
<td style="text-align: left;">经济学的思维方式</td>
<td style="text-align: left;">[美] 保罗·海恩 彼得·勃特克 大卫·普雷契特科</td>
</tr>
<tr>
<td style="text-align: left;">杀死一只知更鸟</td>
<td style="text-align: left;">[美] 哈珀·李</td>
</tr>
<tr>
<td style="text-align: left;">斯通纳</td>
<td style="text-align: left;">[美] 约翰·威廉斯</td>
</tr>
<tr>
<td style="text-align: left;">法治的细节</td>
<td style="text-align: left;">罗翔</td>
</tr>
<tr>
<td style="text-align: left;">一个叫欧维的男人决定去死</td>
<td style="text-align: left;">[瑞典] 弗雷德里克·巴克曼</td>
</tr>
<tr>
<td style="text-align: left;">珍珠</td>
<td style="text-align: left;">[美] 约翰·斯坦贝克</td>
</tr>
<tr>
<td style="text-align: left;">产业与文明</td>
<td style="text-align: left;">张笑宇</td>
</tr>
<tr>
<td style="text-align: left;">小红马</td>
<td style="text-align: left;">[美] 约翰·斯坦贝克</td>
</tr>
<tr>
<td style="text-align: left;">好的心理治疗只需一次</td>
<td style="text-align: left;">涂道坤</td>
</tr>
<tr>
<td style="text-align: left;">煎饼坪</td>
<td style="text-align: left;">[美] 约翰·斯坦贝克</td>
</tr>
<tr>
<td style="text-align: left;">月亮下去了</td>
<td style="text-align: left;">[美] 约翰·斯坦贝克</td>
</tr>
<tr>
<td style="text-align: left;">烦恼的冬天</td>
<td style="text-align: left;">[美] 约翰·斯坦贝克</td>
</tr>
<tr>
<td style="text-align: left;">食南之徒</td>
<td style="text-align: left;">马伯庸</td>
</tr>
<tr>
<td style="text-align: left;">江南困局</td>
<td style="text-align: left;">唐元鹏</td>
</tr>
<tr>
<td style="text-align: left;">他影响了中国：陈云</td>
<td style="text-align: left;">叶永烈</td>
</tr>
<tr>
<td style="text-align: left;">历史转折中的邓小平</td>
<td style="text-align: left;">龙平平</td>
</tr>
<tr>
<td style="text-align: left;">中国官僚政治研究</td>
<td style="text-align: left;">王亚南</td>
</tr>
</tbody>
</table>
]]></content:encoded>
					
					<wfw:commentRss>https://godorz.info/2025/01/2024/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>2023 年小结</title>
		<link>https://godorz.info/2024/01/2023/</link>
					<comments>https://godorz.info/2024/01/2023/#respond</comments>
		
		<dc:creator><![CDATA[ripwu]]></dc:creator>
		<pubDate>Wed, 03 Jan 2024 16:00:20 +0000</pubDate>
				<category><![CDATA[My Thoughts]]></category>
		<guid isPermaLink="false">https://godorz.info/?p=2122</guid>

					<description><![CDATA[今年读了一些书，这里列一下备忘。 台湾出版社 心靈工坊 有句宣传语 Reading as Healing，我很喜欢。 他改变了中国 罗伯特·劳伦斯·库恩 党员、党权与党争 : 1924～1949年中国国民党的组织形态 王奇生 问道马克思：为什么信仰马克思主义？ 董振华 大众哲学 艾思奇 政治学通识 包刚升 哲学与生活 艾思奇 中国现代哲学史 冯友兰 哲学家们都干了些什么？ 林欣浩 政治学十五讲 燕继荣 当代中国社会分层 李强 中国历史的教训 习骅 中国国家治理现代化 胡鞍钢 当代中国政治 许耀桐 当代中国的中央地方关系 周飞舟 以利为利：财政关系与地方政府行为 周飞舟 现代中国的形成（1600-1949） 李怀印 中国：增长放缓之谜 周天勇 现代中国的历程（增订本） 黄仁宇 老人与海 欧内斯特·海明威 长安的荔枝 马伯庸 苏菲的世界 乔斯坦·贾德 蛤蟆先生去看心理医生 罗伯特·戴博德 柳林风声 肯尼思·格雷厄姆 [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>今年读了一些书，这里列一下备忘。</p>
<p>台湾出版社 心靈工坊 有句宣传语 Reading as Healing，我很喜欢。</p>
<table>
<thead>
<tr>
<th style="text-align: left;"></th>
<th style="text-align: left;"></th>
<th style="text-align: left;"></th>
<th style="text-align: left;"></th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">他改变了中国</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">罗伯特·劳伦斯·库恩</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">党员、党权与党争 : 1924～1949年中国国民党的组织形态</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">王奇生</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">问道马克思：为什么信仰马克思主义？</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">董振华</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">大众哲学</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">艾思奇</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">政治学通识</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">包刚升</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">哲学与生活</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">艾思奇</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">中国现代哲学史</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">冯友兰</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">哲学家们都干了些什么？</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">林欣浩</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">政治学十五讲</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">燕继荣</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">当代中国社会分层</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">李强</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">中国历史的教训</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">习骅</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">中国国家治理现代化</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">胡鞍钢</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">当代中国政治</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">许耀桐</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">当代中国的中央地方关系</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">周飞舟</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">以利为利：财政关系与地方政府行为</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">周飞舟</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">现代中国的形成（1600-1949）</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">李怀印</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">中国：增长放缓之谜</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">周天勇</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">现代中国的历程（增订本）</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">黄仁宇</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">老人与海</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">欧内斯特·海明威</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">长安的荔枝</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">马伯庸</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">苏菲的世界</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">乔斯坦·贾德</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">蛤蟆先生去看心理医生</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">罗伯特·戴博德</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">柳林风声</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">肯尼思·格雷厄姆</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">被讨厌的勇气</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">岸见一郎</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">幸福的勇气</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">岸见一郎</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">也许你该找个人聊聊</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">洛莉·戈特利布</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">诊疗椅上的谎言</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">欧文·亚隆</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">当尼采哭泣</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">欧文·亚隆</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">心理学简史</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">颜雅琴</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">妈妈及生命的意义</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">欧文·亚隆</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">生命的礼物</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">欧文·亚隆</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">叔本华的治疗</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">欧文·亚隆</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">爱情刽子手</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">欧文·亚隆</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">为什么我们总是在防御</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">约瑟夫·布尔戈</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">5%的改变</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">李松蔚</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">难道一切都是我的错吗？</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">李松蔚</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">心理医生的故事盒子</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">豪尔赫·布卡伊</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">世界尽头的咖啡馆</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">约翰·史崔勒基</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">大宋河山可骑驴</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">王这么</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">这才是心理学</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">基思·斯坦诺维奇</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">亲密关系</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">罗兰·米勒</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">西线无战事</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">埃里希·玛丽亚·雷马克</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">局外人</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">阿尔贝·加缪</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">芯片战争</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">克里斯·米勒</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">太白金星有点烦</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">马伯庸</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">成为我自己：欧文·亚隆回忆录</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">欧文·亚隆</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">寻觅意义</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">王德峰</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">悉达多</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">赫尔曼·黑塞</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">格式塔心理咨询理论与实践</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">王铮</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">西方哲学史：卷三-近代哲学</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">伯特兰·罗素</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">刘擎西方现代思想讲义</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">刘擎</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">愤怒的葡萄</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">约翰·斯坦贝克</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">一日浮生</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">欧文·亚隆</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">存在主义咖啡馆</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">莎拉·贝克韦尔</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">存在主义心理学的邀请</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">博·雅各布森</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">罐头厂街</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">约翰·斯坦贝克</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">刀锋</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">威廉·萨默塞特·毛姆</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">月亮和六便士</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">威廉·萨默塞特·毛姆</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">面纱</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">威廉·萨默塞特·毛姆</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">非理性的人</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">威廉·巴雷特</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">伊万·伊利奇之死</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">列夫·托尔斯泰</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">存在的艺术</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">艾里希·弗洛姆</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">你当像鸟飞往你的山</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">塔拉·韦斯特弗</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">昨日的世界</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">斯蒂芬·茨威格</td>
<td style="text-align: left;"></td>
</tr>
</tbody>
</table>
]]></content:encoded>
					
					<wfw:commentRss>https://godorz.info/2024/01/2023/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>2022 年小结</title>
		<link>https://godorz.info/2023/01/2022/</link>
					<comments>https://godorz.info/2023/01/2022/#comments</comments>
		
		<dc:creator><![CDATA[ripwu]]></dc:creator>
		<pubDate>Mon, 02 Jan 2023 15:50:11 +0000</pubDate>
				<category><![CDATA[My Thoughts]]></category>
		<guid isPermaLink="false">https://godorz.info/?p=1922</guid>

					<description><![CDATA[有一首诗写道：“黄色的树林里分出两条路，可惜我不能同时去涉足...” 感染了新冠还在恢复，我半躺在窗边，回想起这几年，有种白云苍狗的感觉：人生选择，如这新冠政策般，来了个急转弯。 究其原因，外部世界不确定，内心决策却又难免非理性。人在非理性时，是该停下来思考的。 我对着路口久久思考。 也许，无论踏往何方，让人迷失的，总是另一路风景独好。可惜世间没有分身之术，能让人去探索多条小道上那迷人的未知。幸而，劈柴担水，无非妙道。这一层的精彩，在那一层看来是平常；这一时的平常，在那一刻却显得精彩。山是山，水是水。纵使百尺竿头，再进一步，山还是山，水还是水。大道三千，而又殊途同归，没有什么本质上的不同。 我也思考了终点的问题。 一段路途，如果只用精彩来定义，那么精彩过后，路途是什么？ 路途结束，也还有别的事要做。可是要做的，不也仍然是平常的事？ 有此疑惑，并不是说终点缥缈，因而当下得过且过；也不是说时运莫测，因而否定个人努力。恰恰相反，路只能是走出来的，道就在行之中。不努力进取，就只能在原地打转。 精彩与平常并非泾渭分明，主客观条件在起变化，它们也在转化。但是，精彩是不稳定的，只有平常才是常态。以平常心行百里，会走得更从容些。道路弯曲，却又起伏向前，每一次转弯，不正是自我成长的时机？往者已矣，来者可追，对自我的否定之否定，不正是自我完善的过程？坐而论道，不如躬身入局；骑驴觅驴，不如上下求索。此刻正当收拾精神，潜心修行。 我还思考了同行的问题。 辩证的说，人是一切社会关系的总和，人不可能摆脱社会关系而存在。吾剑未尝不利，虽有良机，不如待时。情缘未断，意犹未尽，不如归来。我知道这有些非理性了，然而性情如此，那就如此吧。眼下，我只想好好走路，体会路上的风景。 一时精彩，不足以让人迷恋；一时平常，不足以让人气短。 拍拍尘土，我又起身，迈向树林深处。 我深深记得，这首诗的结尾是： “也许多少年后在某个地方，我将轻声叹息将往事回顾...” &#160; 今年读了一些书 人鼠之间 约翰 · 斯坦贝克 没有人给他写信的上校 加西亚 · 马尔克斯 太阳照常升起 海明威 呐喊 鲁迅 彷徨 鲁迅 我与地坛 史铁生 平凡的世界 路遥 岁月 电视剧 胡军 / 梅婷 / 于和伟 沧浪之水 阎真 钢铁是怎样炼成的 奥斯特洛夫斯基 罪与罚 陀思妥耶夫斯基 中国的选择：中美博弈与战略抉择 [新加坡] 马凯硕 写作是门手艺 刘军强 宏观经济学（第十版） [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>有一首诗写道：“黄色的树林里分出两条路，可惜我不能同时去涉足...”</p>
<p>感染了新冠还在恢复，我半躺在窗边，回想起这几年，有种白云苍狗的感觉：人生选择，如这新冠政策般，来了个急转弯。</p>
<p>究其原因，外部世界不确定，内心决策却又难免非理性。人在非理性时，是该停下来思考的。</p>
<p>我对着路口久久思考。</p>
<p>也许，无论踏往何方，让人迷失的，总是另一路风景独好。可惜世间没有分身之术，能让人去探索多条小道上那迷人的未知。幸而，劈柴担水，无非妙道。这一层的精彩，在那一层看来是平常；这一时的平常，在那一刻却显得精彩。山是山，水是水。纵使百尺竿头，再进一步，山还是山，水还是水。大道三千，而又殊途同归，没有什么本质上的不同。</p>
<p>我也思考了终点的问题。</p>
<p>一段路途，如果只用精彩来定义，那么精彩过后，路途是什么？</p>
<p>路途结束，也还有别的事要做。可是要做的，不也仍然是平常的事？</p>
<p>有此疑惑，并不是说终点缥缈，因而当下得过且过；也不是说时运莫测，因而否定个人努力。恰恰相反，路只能是走出来的，道就在行之中。不努力进取，就只能在原地打转。</p>
<p>精彩与平常并非泾渭分明，主客观条件在起变化，它们也在转化。但是，精彩是不稳定的，只有平常才是常态。以平常心行百里，会走得更从容些。道路弯曲，却又起伏向前，每一次转弯，不正是自我成长的时机？往者已矣，来者可追，对自我的否定之否定，不正是自我完善的过程？坐而论道，不如躬身入局；骑驴觅驴，不如上下求索。此刻正当收拾精神，潜心修行。</p>
<p>我还思考了同行的问题。</p>
<p>辩证的说，人是一切社会关系的总和，人不可能摆脱社会关系而存在。吾剑未尝不利，虽有良机，不如待时。情缘未断，意犹未尽，不如归来。我知道这有些非理性了，然而性情如此，那就如此吧。眼下，我只想好好走路，体会路上的风景。</p>
<p>一时精彩，不足以让人迷恋；一时平常，不足以让人气短。</p>
<p>拍拍尘土，我又起身，迈向树林深处。</p>
<p>我深深记得，这首诗的结尾是：</p>
<p>“也许多少年后在某个地方，我将轻声叹息将往事回顾...”</p>
<div class="post-footer">&nbsp;</div>
<p>今年读了一些书</p>
<table>
<thead>
<tr>
<th style="text-align: left;"></th>
<th style="text-align: left;"></th>
<th style="text-align: left;"></th>
<th style="text-align: left;"></th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">人鼠之间</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">约翰 · 斯坦贝克</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">没有人给他写信的上校</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">加西亚 · 马尔克斯</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">太阳照常升起</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">海明威</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">呐喊</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">鲁迅</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">彷徨</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">鲁迅</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">我与地坛</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">史铁生</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">平凡的世界</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">路遥</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">岁月</td>
<td style="text-align: left;">电视剧</td>
<td style="text-align: left;">胡军 / 梅婷 / 于和伟</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">沧浪之水</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">阎真</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">钢铁是怎样炼成的</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">奥斯特洛夫斯基</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">罪与罚</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">陀思妥耶夫斯基</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">中国的选择：中美博弈与战略抉择</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">[新加坡] 马凯硕</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">写作是门手艺</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">刘军强</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">宏观经济学（第十版）</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">格里高利 · 曼昆</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">微观经济学（第七版）</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">格里高利 · 曼昆</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">《凡尔赛和约》的经济后果</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">约翰 · 凯恩斯</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">货币的教训：货币与汇率系列评论</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">周其仁</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">世事胜棋局</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">周其仁</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">中国的经济制度</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">张五常</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">经济十八讲 - 现代经济学读书札记</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">樊纲</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">置身事内 ：中国政府与经济发展</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">兰小欢</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">转型中的地方政府：官员激励与治理</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">周黎安</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">小镇喧嚣：一个乡镇政治运作的演绎与阐释</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">吴毅</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">权利结构，政治经历和经济增长：基于浙江民营经济发展经验的政治经济学分析</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">章奇 / 刘明兴</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">大国治理：发展与平衡的空间政治经济学</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">陆铭</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">大国大城：当代中国的统一、发展与平衡</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">陆铭</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">大国领导力</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">阎学通</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">重启改革议程：中国经济改革二十讲</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">吴敬琏 / 马国川</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">筚路维艰：中国社会主义路径的五次选择</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">萧冬连</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">探路之役：1978-1992年的中国经济制度</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">萧冬连</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">毛泽东传（全6卷）</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">中央文献出版社</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">《共产党宣言》纪念版 : 马克思诞辰200周年纪念版</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">人民出版社</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">蒋介石与现代中国</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">[美] 陶涵</td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">中国哲学简史</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">冯友兰</td>
<td style="text-align: left;"></td>
</tr>
</tbody>
</table>
<div class="post-footer">&nbsp;</div>
<p>陪小孩通关了两个双人游戏</p>
<table>
<thead>
<tr>
<th style="text-align: left;"></th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">超级马里奥：奥德赛</td>
</tr>
<tr>
<td style="text-align: left;">毛线小精灵 2</td>
</tr>
</tbody>
</table>
]]></content:encoded>
					
					<wfw:commentRss>https://godorz.info/2023/01/2022/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
		<item>
		<title>Optimism 源码浅析</title>
		<link>https://godorz.info/2022/04/optimism-notes/</link>
					<comments>https://godorz.info/2022/04/optimism-notes/#comments</comments>
		
		<dc:creator><![CDATA[ripwu]]></dc:creator>
		<pubDate>Thu, 28 Apr 2022 03:15:50 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[Optimism]]></category>
		<guid isPermaLink="false">https://godorz.info/?p=1851</guid>

					<description><![CDATA[之前学习 Optimism 的笔记 代码版本：Optimism: 36efcdec48de221d402edf54b653e3403894607d 注解版本：Optimism-annotated 架构概览 图片来自 Optimistic Ethereum Introduction 角色 Optimism 中所有组件目前都由官方运行 L1 CanonicalTransactionChain 功能： 记录批量区块状态 协助中继 L1-&#62;L2 交易 事件： TransactionEnqueued SequencerBatchAppended StateCommitmentChain 功能： 记录批量区块状态 协助 L2-&#62;L1 交易的存在性证明 事件： StateBatchAppended L2 Sequencer 执行两种交易：L2-&#62;L2，L1-&#62;L2 定序器：敲定两种交易的顺序 batch-submitter 打包批量交易 txBatch 提交到 L1 TransactionBatchSubmitter 出于 data availability，把交易按敲定的顺序，序列化后作为 calldata 提交到 L1 打包批量状态 stateBatch 提交到 L1 StateBatchSubmitter 参考 NOTICE: [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>之前学习 Optimism 的笔记</p>
<p>代码版本：<a href="https://github.com/ethereum-optimism/optimism/tree/36efcdec48de221d402edf54b653e3403894607d">Optimism: 36efcdec48de221d402edf54b653e3403894607d</a></p>
<p>注解版本：<a href="https://github.com/ripwu/optimism-annotated">Optimism-annotated</a></p>
<h2>架构概览</h2>
<p><img decoding="async" src="/wp-content/uploads/2022/04/layer2_Optimism_inside_sequencer-handling-deposits-and-transactions.png" alt="Sequencer Syncing Deposits &amp; Accepting a Transaction" /></p>
<p>图片来自 <a href="https://github.com/ethereum-optimism/optimistic-specs/blob/alpha/introduction.md">Optimistic Ethereum Introduction</a></p>
<h3>角色</h3>
<p>Optimism 中所有组件目前都由官方运行</p>
<h4>L1</h4>
<h5>CanonicalTransactionChain</h5>
<p>功能：</p>
<ul>
<li>记录批量区块状态</li>
<li>协助中继 L1-&gt;L2 交易</li>
</ul>
<p>事件：</p>
<ul>
<li>TransactionEnqueued</li>
<li>SequencerBatchAppended</li>
</ul>
<h5>StateCommitmentChain</h5>
<p>功能：</p>
<ul>
<li>记录批量区块状态</li>
<li>协助 L2-&gt;L1 交易的存在性证明</li>
</ul>
<p>事件：</p>
<ul>
<li>StateBatchAppended</li>
</ul>
<h4>L2</h4>
<h5>Sequencer</h5>
<p>执行两种交易：L2-&gt;L2，L1-&gt;L2</p>
<p>定序器：敲定两种交易的顺序</p>
<h5>batch-submitter</h5>
<ul>
<li>
<p>打包批量交易 <code>txBatch</code> 提交到 L1</p>
<ul>
<li>TransactionBatchSubmitter</li>
<li>出于 data availability，把交易按敲定的顺序，序列化后作为 calldata 提交到 L1</li>
</ul>
</li>
<li>
<p>打包批量状态 <code>stateBatch</code> 提交到 L1</p>
<ul>
<li>StateBatchSubmitter</li>
</ul>
</li>
</ul>
<p>参考 <a href="https://github.com/ethereum-optimism/optimism/discussions/1444">NOTICE: TypeScript batch submitter to be deprecated in favor of Go batch submitter #2050</a>，目前 batch-submitter 有 TypeScript 和 Go 两种实现，其中 TypeScript 版本可能会被移除</p>
<p>--</p>
<p>Optimism 目前对交易数据的处理只是简单的序列化，没有压缩</p>
<p>根据文档 <a href="https://medium.com/ethereum-optimism/the-road-to-sub-dollar-transactions-part-2-compression-edition-6bb2890e3e92">Optimism PBC: The Road to Sub-dollar Transactions, Part 2: Compression Edition</a>，Optimism 正在测试使用 zlib 压缩批量交易；另外，后续会采用 <a href="https://eips.ethereum.org/EIPS/eip-4844">EIP-4844</a> 交易格式来降低成本</p>
<h5>data-transport-layer</h5>
<p>索引 L1 事件，并存储到数据库中</p>
<ul>
<li>TransactionEnqueued</li>
<li>SequencerBatchAppended</li>
<li>StateBatchAppended</li>
</ul>
<p>对于 L1-&gt;L2 交易，还将建立引用 index &lt;-&gt; queueIndex</p>
<p>Sequencer 向它查询待执行的 L1-&gt;L2 交易</p>
<h5>relayer</h5>
<p>实时监控已过挑战期的 L2-&gt;L1 交易，在 L1 上调用合约完成中继</p>
<h3>交互</h3>
<p>1.用户在 L1 调用 <code>CanonicalTransactionChain.enqueue()</code>，触发事件 <code>TransactionEnqueued</code></p>
<p>2.<code>data-transport-layer</code> 监听 <code>TransactionEnqueued</code>，解析事件并查出 calldata，写入数据库</p>
<p>3.<code>Sequencer</code> 从 <code>data-transport-layer</code> 同步 <code>TransactionEnqueued</code> 事件，转为交易并执行</p>
<p>4.或者，用户可通过 L2 RPC 节点，直接向 <code>Sequencer</code> 发送交易</p>
<p>56.<code>batch-submitter</code> 监听 L2 区块，打包 txBatch 提交到 L1 合约 <code>CanonicalTransactionChain.appendSequencerBatch()</code>，触发 <code>TransactionBatchSubmitter</code> 事件</p>
<p>参考 <a href="https://github.com/ethereum-optimism/optimism/blob/ae4a90d920d84ae7b6348bd6cb934775d139cbaa/packages/batch-submitter/src/batch-submitter/tx-batch-submitter.ts#L672">tx-batch-submitter.ts: _getSequencerBatchParams()</a></p>
<p>789.<code>batch-submitter</code> 监听 L2 区块，打包 stateBatch 提交到 L1 合约 <code>StateCommitmentChain.appendStateBatch()</code>，触发 <code>StateBatchSubmitter</code> 事件；对应的区块进入挑战期</p>
<p>参考 <a href="https://github.com/ethereum-optimism/optimism/blob/ca547c4e40393d2b5c4c97dcbf9c97f0ac8f35fe/packages/batch-submitter/src/batch-submitter/state-batch-submitter.ts#L205">state-batch-submitter.ts: _generateStateCommitmentBatch()</a></p>
<p>其中，Optimism 目前正在重构第 8 步欺诈证明的实现，详见下文</p>
<h2>欺诈证明</h2>
<p>Optimistic Rollup 对 状态转移 的保证是在一定时间窗口内，允许挑战者提交欺诈证明，表示对某一状态存在质疑，并开始仲裁。仲裁有两种方式：</p>
<ul>
<li>非交互式欺诈证明
<ul>
<li>在 L1 重新执行一个区块</li>
<li>Optimism 采用</li>
</ul>
</li>
<li>交互式欺诈证明
<ul>
<li>在 L2 上由双方进行多轮交互后锁定某条存疑指令后在 L1 仲裁</li>
<li>Arbitrum 采用</li>
</ul>
</li>
</ul>
<p>Optimism 采用 非交互式欺诈证明，需要在 L1 重新执行一个区块，而 L1 执行成本高昂且存在 gasLimit 限制，因此要求直接指定区块内的哪一笔交易存在问题，以此在 L1 上只验证这一笔交易，而非从头执行这个区块。</p>
<p>在 Optimism 实现中，一个 L2 区块只包含一笔交易，使得区块的 <code>stateRoot</code> 实际就是这笔交易的 <code>stateRoot</code>；即每笔交易都会有一个对应的状态被提交到 L1，因此它可以被单独挑战</p>
<p>这个设计类似 <a href="https://www.vitalik.ca/general/2021/12/06/endgame.html">Vitalik: Endgame</a> 前期架构</p>
<p><img decoding="async" src="/wp-content/uploads/2022/04/layer2_Optimism_inside_endgame.png" alt="Endgame" /></p>
<p>--</p>
<p>根据 <a href="https://community.optimism.io/docs/protocol/#">Next gen fault proofs</a>，Optimism 目前暂停了 非交互式欺诈证明 (用户必须信任 Optimism 运行的节点没有作恶)，计划在今年转为 交互式欺诈证明</p>
<h2>定序和时间</h2>
<h3>交易定序</h3>
<p>两种交易的顺序，是由 Sequencer 按到达时间确定的</p>
<p>假设存在如下交易：</p>
<ul>
<li>t0: L1-&gt;L2 交易 Tx0，假设在 t3 被 data-transport-layer 索引 (经过多个 L1 区块的确认)</li>
<li>t1: L2-&gt;L2 交易 Tx1</li>
<li>t2: L2-&gt;L2 交易 Tx2</li>
<li>t4: L2-&gt;L2 交易 Tx3</li>
</ul>
<p>那么，这些交易在 L2 的执行时间为 [t1, t2, t3, t4]，交易顺序为 [Tx1, Tx2, Tx0, Tx3]；每笔交易为一个区块，按出块顺序记为 [B0, B1, B2, B3]</p>
<p>需要说明的是，区块头部中的区块时间，并非交易执行时间，而是更小些，比如 B2.header.timestamp &lt; t3；详见后文</p>
<h3>区块时间</h3>
<p>相关文档</p>
<ul>
<li><a href="https://docs.uniswap.org/protocol/concepts/V3-overview/oracle#oracles-integrations-on-layer-2-rollups">Uniswap V3: Oracles Integrations on Layer 2 Rollups</a></li>
<li><a href="https://github.com/ethereum-optimism/optimistic-specs/discussions/23">How should we handle <code>timestamp</code> in v1.0? #23</a></li>
</ul>
<p>Sequencer 维护了 LatestL1Timestamp，每 timestampRefreshThreshold 更新一次 LatestL1Timestamp，且对两种交易都设置了 <code>tx.L1Timestamp</code></p>
<p>其中，timestampRefreshThreshold 配置为 15 秒 (启动参数 <a href="https://github.com/ethereum-optimism/optimism/blob/e631c39cb4abca63bbbce52052df1c5fc85f5c75/l2geth/scripts/start.sh#L13">ROLLUP_TIMESTAMP_REFRESH</a> 和 <a href="https://optimistic.etherscan.io/txs">浏览器</a>)</p>
<pre><code class="language-go">func (s *SyncService) applyTransactionToTip(tx *types.Transaction) error {
    // The property that L1 to L2 transactions have the same timestamp as the
    // L1 block that it was included in is removed for better UX.
    ts := s.GetLatestL1Timestamp()
    bn := s.GetLatestL1BlockNumber()

    shouldMalleateTimestamp := !s.verifier &amp;&amp; tx.QueueOrigin() == types.QueueOriginL1ToL2
    if tx.L1Timestamp() == 0 || shouldMalleateTimestamp {
        // 两种交易都会进来

        // Get the latest known timestamp
        current := time.Unix(int64(ts), 0)
        // Get the current clocktime
        now := time.Now()

        // If enough time has passed, then assign the
        // transaction to have the timestamp now. Otherwise,
        // use the current timestamp
        if now.Sub(current) &gt; s.timestampRefreshThreshold {
            current = now
        }

        log.Info(&quot;Updating latest timestamp&quot;, &quot;timestamp&quot;, current, &quot;unix&quot;, current.Unix())

        // 将两种交易的 L1Timestamp 改为 LatestL1Timestamp 或 当前时间
        tx.SetL1Timestamp(uint64(current.Unix()))
    }

    // 更新 LatestL1Timestamp
    // Store the latest timestamp value
    if tx.L1Timestamp() &gt; ts {
        s.SetLatestL1Timestamp(tx.L1Timestamp())
    }
}</code></pre>
<p>--</p>
<p><code>tx.L1Timestamp</code> 用途如下：</p>
<p>1.在执行交易前，构造区块头，作为区块时间</p>
<pre><code class="language-go">// l2geth/miner/worker.go
func (w *worker) commitNewTx(tx *types.Transaction) error {
    // 使用 l1 的区块时间
    header := &amp;types.Header{
        ParentHash: parent.Hash(),
        Number:     new(big.Int).Add(num, common.Big1),
        GasLimit:   w.config.GasFloor,
        Extra:      w.extra,
        Time:       tx.L1Timestamp(),
    }
}</code></pre>
<p>2.在交易执行时，构造 EVM 上下文，作为 opTimestamp 取值</p>
<pre><code class="language-go">// l2geth/core/evm.go
func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author *common.Address) vm.Context {
    // 参考 [Differences between Ethereum and Optimism](https://community.optimism.io/docs/developers/build/differences/#)
    if rcfg.UsingOVM {
        // When using the OVM, we must:
        // - Set the Time to be the msg.L1Timestamp
        return vm.Context{
            // 使得 opTimestamp 取值是 L1Timestamp
            Time:          new(big.Int).SetUint64(msg.L1Timestamp()),
        }
    }
}</code></pre>
<h3>共识算法</h3>
<p><a href="https://ethereum.stackexchange.com/questions/9045/why-does-ethereum-creates-a-new-block-without-even-a-single-transaction">Why does ethereum creates a new block,without even a single transaction?</a></p>
<p>根据上文，多笔交易可能使用相同的区块时间，又因为每一笔交易为一个区块，所以存在某个区块，其区块时间可能等于父区块的区块时间；</p>
<p>这在 PoW 共识算法中是不允许的：</p>
<pre><code class="language-go">// l2geth/consensus/ethash/consensus.go

// verifyHeader checks whether a header conforms to the consensus rules of the
// stock Ethereum ethash engine.
// See YP section 4.3.4. &quot;Block Header Validity&quot;
func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *types.Header, uncle bool, seal bool) error {
    if header.Time &lt;= parent.Time {
        return errOlderBlockTime
    }

    // ...
}</code></pre>
<p>根据 <a href="https://github.com/ethereum-optimism/optimism/blob/e631c39cb4abca63bbbce52052df1c5fc85f5c75/packages/contracts/src/make-genesis.ts#L156">make-genesis.ts</a>，Optimism 共识采用 PoA，但 PoA 对区块时间也有要求，参考 <a href="https://github.com/ethereum/go-ethereum/issues/21184">Multiple blocks with same timestamp in clique network. #21184</a></p>
<p>因此，Optimism 修改了相关代码，在 Clique 共识中跳过了区块时间的检查，比如：</p>
<pre><code class="language-go">// l2geth/consensus/clique/clique.go

func (c *Clique) verifyHeader(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
    if !rcfg.UsingOVM {
        // Don&#039;t waste time checking blocks from the future
        if header.Time &gt; uint64(time.Now().Unix()) {
            return consensus.ErrFutureBlock
        }
    }

    // ...
}

func (c *Clique) verifyCascadingFields(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
    if !rcfg.UsingOVM {
        if parent.Time+c.config.Period &gt; header.Time {
            return ErrInvalidTimestamp
        }
    }

    // ...
}</code></pre>
<h2>区块回滚</h2>
<p>根据文档，代码和 issues， Optimism 在开发时考虑了 L1 发生区块回滚的情况，但未完全处理；比如：</p>
<pre><code class="language-go">// l2geth/rollup/sync_service.go

// When reorg logic is enabled, this should also call `syncBatchesToTip`
func (s *SyncService) sequence() error {
    // ...
}

// This will trigger a reorg in the future
func (s *SyncService) applyHistoricalTransaction(tx *types.Transaction) error {
    // ...
}</code></pre>
<p>理解：</p>
<p>L1 区块回滚分两种情况：1. 用户在 L1 发起的交易被回滚；2. L2 在 L1 发起的交易被回滚</p>
<p>对于前者，典型操作为 L1-&gt;L2 的消息传递，data-transport-layer 会等待 <a href="https://github.com/ethereum-optimism/optimism/blob/baece507896b3547be62923395eb8097d0a4cc32/packages/data-transport-layer/src/services/l1-ingestion/service.ts#L70">L1 上 35 个区块确认</a> 才做索引，回滚概率小</p>
<p>对于后者，Sequencer 和 batch-submitter 目前都是中心化的，不会存在竞争。即使 L1 回滚，此前提交的交易和状态也会被后续区块重新打包，只是 区块高度 和 区块哈希 不同，而 Optimism 不依赖于这两者</p>
<h2>消息传递</h2>
<p>相关文档</p>
<ul>
<li><a href="https://community.optimism.io/docs/developers/bridge/messaging/">Sending data between L1 and L2</a></li>
<li><a href="https://community.optimism.io/docs/how-optimism-works/#bridging-assets-between-layers">How Optimism Works #Bridging assets between layers</a></li>
</ul>
<h3>L1-&gt;L2</h3>
<h4>流程概览</h4>
<p>1.用户或合约在 L1 调用 <code>CanonicalTransactionChain.enqueue()</code>，触发事件 <code>TransactionEnqueued</code></p>
<p>参考 <a href="https://github.com/ethereum-optimism/optimism/blob/8f91dde312446ca596b610a9efdb2ba819d781fb/packages/contracts/contracts/L1/rollup/CanonicalTransactionChain.sol#L210">CanonicalTransactionChain.enqueue()</a></p>
<p>2.<code>data-transport-layer</code> 监听 <code>TransactionEnqueued</code>，解析后写入数据库</p>
<p>参考 <a href="https://github.com/ethereum-optimism/optimism/blob/a7239b1516b710953733b8d555e4be7af077dfc6/packages/data-transport-layer/src/services/l1-ingestion/handlers/transaction-enqueued.ts#L9">transaction-enqueued.ts: handleEventsTransactionEnqueued</a></p>
<p>3.<code>Sequencer</code> 从 <code>data-transport-layer</code> 查询 <code>TransactionEnqueued</code> 事件，转为交易并执行，挖出对应区块</p>
<p>参考 <a href="https://github.com/ethereum-optimism/optimism/blob/9a4f9ad27cbe0fa42852e4f7b8a853814b0e8a62/l2geth/rollup/sync_service.go#L1175">sync_service.go: SyncService.syncQueueTransactionRange()</a></p>
<p>调用层次较深：<code>SequencerLoop()</code> -&gt; ... -&gt; <code>syncQueueToTip()</code> -&gt; ... -&gt; <code>syncQueueTransactionRange()</code></p>
<h4>交易处理</h4>
<h5>L1 合约调用</h5>
<pre><code class="language-js">// packages/contracts/contracts/L1/rollup/CanonicalTransactionChain.sol
contract CanonicalTransactionChain {
    uint40 private _nextQueueIndex; // index of the first queue element not yet included
    Lib_OVMCodec.QueueElement[] queueElements;

    function enqueue(address _target, uint256 _gasLimit, bytes memory _data) external {
        address sender;
        if (msg.sender == tx.origin) {
            sender = msg.sender;
        } else {
            sender = AddressAliasHelper.applyL1ToL2Alias(msg.sender);
        }

        bytes32 transactionHash = keccak256(abi.encode(sender, _target, _gasLimit, _data));

        queueElements.push(
            Lib_OVMCodec.QueueElement({
                transactionHash: transactionHash,
                timestamp: uint40(block.timestamp),
                blockNumber: uint40(block.number)
            })
        );

        uint256 queueIndex = queueElements.length - 1;
        emit TransactionEnqueued(sender, _target, _gasLimit, _data, queueIndex, block.timestamp);
    }
}</code></pre>
<p>用户调用 <code>enqueue()</code>，将导致交易被压入 <code>queueElements</code> 队列；其中，<code>_nextQueueIndex</code> 表示队列中前多少条交易已经由 Sequencer 敲定顺序</p>
<p>注意，如果 <code>msg.sender</code> 是个合约地址，会被 <code>AddressAliasHelper</code> 设置偏移：<code>0x1111000000000000000000000000000000001111</code></p>
<h5>L2 交易执行</h5>
<pre><code class="language-go">// l2geth/rollup/client.go

// enqueueToTransaction turns an Enqueue into a types.Transaction
// so that it can be consumed by the SyncService
func enqueueToTransaction(enqueue *Enqueue) (*types.Transaction, error) {
    nonce := *enqueue.QueueIndex
    target := *enqueue.Target
    gasLimit := *enqueue.GasLimit
    origin := *enqueue.Origin

    blockNumber := new(big.Int).SetUint64(*enqueue.BlockNumber)
    timestamp := *enqueue.Timestamp

    data := *enqueue.Data

    // enqueue transactions have no value
    value := big.NewInt(0)
    tx := types.NewTransaction(nonce, target, value, gasLimit, big.NewInt(0), data)

    // The index does not get a check as it is allowed to be nil in the context
    // of an enqueue transaction that has yet to be included into the CTC
    txMeta := types.NewTransactionMeta(
        blockNumber,
        timestamp,
        &amp;origin,
        types.QueueOriginL1ToL2,

        // 对于未定序的 enqueue 消息，enqueue.Index 为 nil，因此 txMeta.Index 也为 nil
        enqueue.Index,

        enqueue.QueueIndex,
        data,
    )

    tx.SetTransactionMeta(txMeta)

    return tx, nil
}</code></pre>
<p>Sequencer 通过 <code>enqueueToTransaction()</code> 将 L1 上发起的一笔交易的事件数据 <code>enqueue</code> 转为 <code>types.Transaction</code>，各字段设置如下：</p>
<ul>
<li>to, gasLimit, data 设置为 L1 调用的对应字段
<ul>
<li>参考 <code>function enqueue(address _target, uint256 _gasLimit, bytes memory _data)</code></li>
</ul>
</li>
<li>value, gasPrice 设置为 0</li>
<li>nonce 设置为 queueIndex</li>
<li>v, r, s 未设置</li>
</ul>
<p>注意：因为 value, gasPrice 为 0，所以中继到 L2 的交易不消耗 gas</p>
<p>另外，还设置了交易的 meta 字段，用途在下文描述</p>
<p>--</p>
<p>L2 Sequencer 还做了如下特殊处理：</p>
<p>由于 nonce 设置为 queueIndex，而 queueIndex 是全局变量，因此对同一 <code>msg.sender</code> 而言，nonce 可能不连续；因此在执行交易前的检查中，对来自 L1 的交易会跳过 nonce 检查</p>
<pre><code class="language-go">// l2geth/core/state_transition.go
func (st *StateTransition) preCheck() error {
    // Make sure this transaction&#039;s nonce is correct.

    // 如果需要检查 nonce
    if st.msg.CheckNonce() {
        if rcfg.UsingOVM {
            // 如果是 L1-&gt;L2 消息，跳过 nonce 检查，直接调用 buyGas()
            if st.msg.QueueOrigin() == types.QueueOriginL1ToL2 {
                return st.buyGas()
            }
        }

        // 正常的检查 nonce 逻辑
        nonce := st.state.GetNonce(st.msg.From())
        if nonce &lt; st.msg.Nonce() {
            return ErrNonceTooHigh
        } else if nonce &gt; st.msg.Nonce() {
            return ErrNonceTooLow
        }
    }
    return st.buyGas()
}</code></pre>
<p>另外，由于无法通过 v, r, s 签名计算交易发起者，因此在通过 EVM 执行交易前，交易转为 <code>Message</code> 时，会从 meta 中取出 <code>L1MessageSender</code> 作为 <code>msg.from</code></p>
<pre><code class="language-go">// l2geth/core/types/transaction.go

func (tx *Transaction) AsMessage(s Signer) (Message, error) {
    var err error
    if rcfg.UsingOVM {
        // transaction 转 Message 时，对于 L1-&gt;L2 的消息，取 meta.L1MessageSender 作为 Origin
        if tx.meta.QueueOrigin == QueueOriginL1ToL2 &amp;&amp; tx.meta.L1MessageSender != nil {
            msg.from = *tx.meta.L1MessageSender
        } else {
            msg.from, err = Sender(s, tx)
        }
    } else {
        msg.from, err = Sender(s, tx)
    }

    return msg, err
}</code></pre>
<p>--</p>
<p>经过上面的处理，L1-&gt;L2 消息被转换为了 L2 上的一笔交易，它将与正常交易一样被执行和打包</p>
<h4>为什么需要 Address Alias</h4>
<p>相关文档</p>
<ul>
<li><a href="https://community.optimism.io/docs/developers/build/differences/#address-aliasing">Why is address aliasing an issue?</a></li>
<li><a href="https://community.optimism.io/docs/developers/build/differences/#using-eth-in-contracts">Differences between Ethereum and Optimism #Address Aliasing</a></li>
</ul>
<p>对于 L1-&gt;L2 消息，如果 <code>msg.sender</code> 为合约，那么在 L2 执行时，<code>Origin</code> 会被加上偏移</p>
<p>这是出于安全考虑，举例如下：</p>
<p>首先，攻击者在 L2 构造一个开放源码的合约，如 Uniswap pair，并诱导用户对合约授权 (<code>approve()</code>)</p>
<p>然后，攻击者在 L1 同一地址部署恶意合约，并通过该恶意合约向 L2 传递攻击消息，取出用户授权的 ERC20 token；参数如下：</p>
<ul>
<li>msg.sender
<ul>
<li>Uniswap pair (L2) / 恶意合约 (L1)</li>
</ul>
</li>
<li>_target
<ul>
<li>ERC20 地址</li>
</ul>
</li>
<li>_data
<ul>
<li><code>transferFrom(userAddress, attackerAddress, amount)</code></li>
</ul>
</li>
</ul>
<p>解决：</p>
<p>经过讨论：<a href="https://github.com/ethereum-optimism/optimism/discussions/1480">What should the value of tx.origin and msg.sender be for L1 to L2 transactions? #1480</a>，决定参考 Arbitrum 的方案，通过 Address Alias 对<code>msg.sender</code> 地址做了偏移；由于哈希的抗碰撞性，攻击者无法计算相关参数 (如 Create nonce 或 Create2 salt)，因此无法将合约部署在偏移前的地址</p>
<h4>如何实现 Censorship resistance</h4>
<p>通过 L1-&gt;L2 消息传递，Optimism 实现了抗 Sequencer 审查某位用户</p>
<p>原因：Sequencer 可以忽略整个 L1-&gt;L2 消息队列，但无法跳过队列中某笔请求；因为对队列的消耗，必须遵守 FIFO 的顺序 (queueIndex 要求递增)</p>
<h4>比较 Arbitrum</h4>
<p>相关文档</p>
<ul>
<li><a href="https://developer.offchainlabs.com/docs/inside_arbitrum#inboxes-fast-and-slow">Inside Arbitrum: Inboxes, fast and slow</a></li>
<li><a href="https://developer.offchainlabs.com/docs/censorship_resistance#unhappyuncommon-case-sequencer-isnt-doing-its-job">The Sequencer and Censorship Resistance: Unhappy/Uncommon Case: Sequencer Isn’t Doing It’s Job</a></li>
</ul>
<p>根据上文，Optimism Sequencer 虽然无法审查某位用户，但可以审查所有 L1-&gt;L2 交易，拒绝执行</p>
<p>与之相对的，Arbitrum 实现了 force inclusion 的功能，抗审查能力更强；两者比较如下：</p>
<p>Arbitrum 和 Optimism 一样，都有两条队列：Sequencer 定序队列，L1-&gt;L2 交易队列</p>
<ul>
<li>Sequencer 定序队列
<ul>
<li>Optimism: <code>ChainStorageContainer-CTC-batches</code></li>
<li>Arbitrum: <code>SequencerInbox</code> (文档也称 fast Inbox)</li>
</ul>
</li>
<li>L1-&gt;L2 交易队列
<ul>
<li>Optimism: <code>CanonicalTransactionChain.queueElements</code></li>
<li>Arbitrum: <code>Inbox</code> (文档也称 delayed Inbox)</li>
</ul>
</li>
</ul>
<p>正常而言，不管是 Optimism 还是 Arbitrum，Sequencer 都会处理 L2 发起的交易，以及消费 L1-&gt;L2 交易队列，对这两种交易定序，定序结果存储在 Sequencer 定序队列中；</p>
<p>区别如下：</p>
<p>在 Optimism 中，只存在唯一一个定序者 Sequencer，两种交易都由它确定顺序；这带来了 Sequencer 拒不处理 L1-&gt;L2 交易队列的风险</p>
<p>相反，在 Arbitrum 中，存在两个定序者：L2 Sequencer 和 L1 合约；如果 L1-&gt;L2 交易队列中的一笔交易L1-&gt;L2 交易经过一段时间 (目前配置为 24 小时) 未被定序，用户可以在 L1 调用合约函数 <code>SequencerInbox.forceInclusion()</code>，强制将它包含进 定序队列</p>
<p>可以预见：Arbitrum 比起 Optimism，其 Sequencer 的实现会更复杂：L1 合约定序，可能会导致后续 L2 Sequencer 回滚</p>
<h3>L2-&gt;L1</h3>
<p>理解如下：</p>
<p>根据 <a href="#欺诈证明">欺诈证明</a>，Optimism 每笔交易都有一个 <code>stateRoot</code> 需要上传到 L1，如果在合约存储，需要消耗大量的 gas</p>
<p>为了节省成本，L1 合约将一批 <code>stateRoot</code> 组织成 Merkle Tree，只存储根哈希；以此支持某个 <code>stateRoot</code> 的存在性证明</p>
<h4>流程概览</h4>
<ol>
<li>
<p>用户或合约在 L2 调用入口函数 <code>L2CrossDomainMessenger.sendMessage()</code>，它进一步调用<br />
<code>OVM_L2ToL1MessagePasser.passMessageToL1()</code>，它计算消息哈希，更新合约状态</p>
</li>
<li>
<p><code>batch-submitter</code> 向 L1 合约提交 <code>txBatch</code> 和 <code>stateBatch</code></p>
</li>
<li>
<p>L1 合约计算 <code>stateBatch</code> 组织而成的 <code>Merkle Tree</code>，存储根哈希</p>
</li>
<li>
<p><code>relayer</code> 监控在 L1 上经过挑战期的 L2-&gt;L1 消息，为其生成证明后，在 L1 调用合约 <code>L1CrossDomainMessenger.relayMessage()</code>；L1 合约验证证明后，调用目标合约</p>
</li>
</ol>
<h4>发起消息</h4>
<pre><code class="language-js">// packages/contracts/contracts/L2/messaging/L2CrossDomainMessenger.sol
contract L2CrossDomainMessenger is IL2CrossDomainMessenger {
    function sendMessage(
        address _target,
        bytes memory _message,
        uint32 _gasLimit
    ) public {
        bytes memory xDomainCalldata = Lib_CrossDomainUtils.encodeXDomainCalldata(
            _target,
            msg.sender,
            _message,
            messageNonce
        );

        iOVM_L2ToL1MessagePasser(Lib_PredeployAddresses.L2_TO_L1_MESSAGE_PASSER).passMessageToL1(
            xDomainCalldata
        );
    }
}

// packages/contracts/contracts/L2/predeploys/OVM_L2ToL1MessagePasser.sol
contract OVM_L2ToL1MessagePasser is iOVM_L2ToL1MessagePasser {
    function passMessageToL1(bytes memory _message) public {
        // Note: although this function is public, only messages sent from the
        // L2CrossDomainMessenger will be relayed by the L1CrossDomainMessenger.
        // This is enforced by a check in L1CrossDomainMessenger._verifyStorageProof().
        sentMessages[keccak256(abi.encodePacked(_message, msg.sender))] = true;
    }
}</code></pre>
<p>Sequencer 在执行 L2-&gt;L1 交易时，将修改 <code>OVM_L2ToL1MessagePasser</code> 状态树 (<code>sentMessages[slot]</code>)，因而进导致 世界树 的变化，反应在 区块头部 的 <code>stateRoot</code> 字段。<code>batch-submitter</code> 将提交 <code>stateRoot</code> 到 L1 并等待挑战</p>
<p>其中，slot 的计算方式为 <code>keccak256(_message + sender)</code></p>
<h4>提交 stateBatch</h4>
<pre><code class="language-js">// packages/contracts/contracts/L1/rollup/StateCommitmentChain.sol

contract StateCommitmentChain is IStateCommitmentChain, Lib_AddressResolver {
    function _appendBatch(bytes32[] memory _batch, bytes memory _extraData) internal {
        address sequencer = resolve(&quot;OVM_Proposer&quot;);
        (uint40 totalElements, uint40 lastSequencerTimestamp) = _getBatchExtraData();

        // 2.组织 batchHeader
        Lib_OVMCodec.ChainBatchHeader memory batchHeader = Lib_OVMCodec.ChainBatchHeader({
            batchIndex: getTotalBatches(),
            // 1. 将多笔 stateRoot 组织成一棵 Merkle Tree，得到 batchRoot；
            batchRoot: Lib_MerkleTree.getMerkleRoot(_batch),
            batchSize: _batch.length,
            prevTotalElements: totalElements,
            extraData: _extraData
        });

        // 3. 存储 batchHeader 到数组，可以方便的通过 batchIndex 索引
        batches().push(
            Lib_OVMCodec.hashBatchHeader(batchHeader),
            _makeBatchExtraData(
                uint40(batchHeader.prevTotalElements + batchHeader.batchSize),
                lastSequencerTimestamp
            )
        );
    }
}</code></pre>
<p><code>batch-submitter</code> 监听 L2 区块，一次批量提交多笔区块的 <code>stateRoot</code> 到 L1 合约</p>
<p>L1 合约处理如下：</p>
<ol>
<li>将多笔 <code>stateRoot</code> 组织成一棵 <code>Merkle Tree</code>，得到根哈希 <code>batchRoot</code></li>
<li>组织 meta 信息，得到 <code>batchHeader</code></li>
<li>存储 <code>batchHeader</code> 到数组 (可以通过 batchIndex 进行索引)</li>
</ol>
<h4>证明生成</h4>
<pre><code class="language-js">// packages/sdk/src/cross-chain-messenger.ts
export class CrossChainMessenger implements ICrossChainMessenger {
  public async getMessageProof(
    message: MessageLike
  ): Promise&lt;CrossChainMessageProof&gt; {
    const resolved = await this.toCrossChainMessage(message)
    if (resolved.direction === MessageDirection.L1_TO_L2) {
      throw new Error(`can only generate proofs for L2 to L1 messages`)
    }

    // 1. 通过 txHash 向 Sequencer 请求 receipt，得到这笔交易所在区块的 stateRoot
    const stateRoot = await this.getMessageStateRoot(resolved)
    if (stateRoot === null) {
      throw new Error(`state root for message not yet published`)
    }

    // 2. 计算 slot
    const messageSlot = ethers.utils.keccak256(
      ethers.utils.keccak256(
        encodeCrossChainMessage(resolved) +
          remove0x(this.contracts.l2.L2CrossDomainMessenger.address)
      ) + &#039;00&#039;.repeat(32)
    )

    // 3. 通过 eth_getProof 请求 Sequencer 生成 slot 的证明
    const stateTrieProof = await makeStateTrieProof(
      this.l2Provider as any,
      resolved.blockNumber,
      this.contracts.l2.OVM_L2ToL1MessagePasser.address,
      messageSlot
    )

    return {
      stateRoot: stateRoot.stateRoot,
      stateRootBatchHeader: stateRoot.batch.header,
      stateRootProof: {
        index: stateRoot.stateRootIndexInBatch,
        // 4. 生成 stateRoot 在 stateBatch 的存在性证明
        siblings: makeMerkleTreeProof(
          stateRoot.batch.stateRoots,
          stateRoot.stateRootIndexInBatch
        ),
      },
      stateTrieWitness: stateTrieProof.accountProof,
      storageTrieWitness: stateTrieProof.storageProof,
    }
  }
}</code></pre>
<p><code>relayer</code> 监控已经过了挑战期的 L2-&gt;L1 交易，为其生成证明并提交到 L1；过程如下：</p>
<ol>
<li>通过 <code>txHash</code> 向 <code>Sequencer</code> 请求 <code>receipt</code>，得到这笔交易所在区块的 <code>stateRoot</code></li>
<li>计算消息的 slot，规则与上面合约的计算方式一致：<code>keccak256(_message + sender)</code></li>
<li>生成 消息在 合约地址 <code>OVM_L2ToL1MessagePasser.address</code> 的存在性的证明 (通过调用 Sequencer <code>eth_getProof</code>)</li>
<li>生成 <code>stateRoot</code> 在此前已提交的 <code>stateBatch</code> 的存在性证明 (通过将 <code>stateBatch</code> 组织成 Merkle Tree)</li>
</ol>
<p>根据 <a href="https://github.com/ethereum-optimism/optimism/discussions/1362">Should we remove the Message Relayer? #1362</a>，<code>relayer</code> 用于测试环境，后续将废弃。开发者需要自己使用 Optimism SDK (开发中) 来桥接交易并承担 L1 gas 费用。</p>
<h4>中继消息</h4>
<pre><code class="language-js">// packages/contracts/contracts/L1/messaging/L1CrossDomainMessenger.sol

contract L1CrossDomainMessenger {
    function relayMessage(
        address _target,
        address _sender,
        bytes memory _message,
        uint256 _messageNonce,
        L2MessageInclusionProof memory _proof
    ) public nonReentrant whenNotPaused {
        bytes memory xDomainCalldata = Lib_CrossDomainUtils.encodeXDomainCalldata(
            _target,
            _sender,
            _message,
            _messageNonce
        );

        // 1. 验证 L2-&gt;L1 交易存在
        require(
            _verifyXDomainMessage(xDomainCalldata, _proof) == true,
            &quot;Provided message could not be verified.&quot;
        );

        bytes32 xDomainCalldataHash = keccak256(xDomainCalldata);

        // 2. 检查交易未曾被中继
        require(
            successfulMessages[xDomainCalldataHash] == false,
            &quot;Provided message has already been received.&quot;
        );

        // 3a. 调用前：修改 xDomainMsgSender 为 _sender
        xDomainMsgSender = _sender;

        // 3b. 消息调用
        (bool success, ) = _target.call(_message);

        // 3c. 调用后：重置 xDomainMsgSender
        xDomainMsgSender = Lib_DefaultValues.DEFAULT_XDOMAIN_SENDER;

        // Mark the message as received if the call was successful. Ensures that a message can be
        // relayed multiple times in the case that the call reverted.
        if (success == true) {
            // 4. 如果调用成功，设置标记
            successfulMessages[xDomainCalldataHash] = true;
            emit RelayedMessage(xDomainCalldataHash);
        } else {
            emit FailedRelayedMessage(xDomainCalldataHash);
        }
    }

    // 辅助函数：获取交易发起者的地址
    function xDomainMessageSender() public view returns (address) {
        require(
            xDomainMsgSender != Lib_DefaultValues.DEFAULT_XDOMAIN_SENDER,
            &quot;xDomainMessageSender is not set&quot;
        );
        return xDomainMsgSender;
    }
}</code></pre>
<p>中继消息的流程如下：</p>
<ol>
<li>验证 L2-&gt;L1 交易存在，分为两步：
<ol>
<li>证明 交易 存在 <code>stateRoot</code> 中</li>
<li>证明 <code>stateRoot</code> 存在 <code>batchRoot</code> (已过挑战期的 stateBatch，参考 <a href="#提交-statebatch">提交 stateBatch</a>) 中</li>
</ol>
</li>
<li>检查消息未曾被成功中继</li>
<li>中继消息，被调用方可以通过 <code>xDomainMessageSender()</code> 获取交易发起者的地址</li>
<li>如果成功，设置标记</li>
</ol>
<h4>比较 Arbitrum</h4>
<p>Optimism 对 L2-&gt;L1 消息传递 在 L1 的处理，与 <a href="https://developer.offchainlabs.com/docs/l1_l2_messages#arbitrum-to-ethereum">Arbitrum 的 Retryable Ticket 设计</a> 类似：</p>
<ol>
<li>没有权限限制，任何人可以调用 <code>relayMessage()</code></li>
<li>没有次数限制，可以多次重试直到成功</li>
<li>没有时间限制</li>
</ol>
<p>理解如下：用户需要自己承担消息在 L1 上的执行费用，即要求用户自己调用 <code>relayMessage()</code></p>
<h2>StandardBridge</h2>
<p><a href="https://community.optimism.io/docs/developers/bridge/standard-bridge/">Using the Standard Token Bridge</a><br />
<a href="https://community.optimism.io/docs/guides/token-dev/">Adding an ERC20 token to the standard bridge</a><br />
<a href="https://community.optimism.io/docs/guides/bridge-dev/#">Adding a custom bridge to Optimism</a></p>
<p>StandardBridge 基于上文描述的消息传递机制，实现 ETH，ERC20 在 L1 与 L2 之间的桥接；但不支持非同质化代币 如 NFT，开发者可以自行通过通用消息传递机制来实现</p>
<p>在实现上，主要为 L2StandardBridge.sol 和 L2StandardBridge.sol，代码比较简单</p>
<p>在应用上，Optimism 基于 StandardBridge 实现了官方桥 <a href="https://gateway.optimism.io/">Optimism Gateway</a>，支持的 token 列表配置在 <a href="https://github.com/ethereum-optimism/ethereum-optimism.github.io/blob/master/optimism.tokenlist.json">optimism.tokenlist.json</a></p>
<p>--</p>
<p>为什么账户 balance 在 Optimism 中以 ERC20 方式存在?</p>
<p>根据 <a href="https://github.com/ethereum-optimism/optimistic-specs/discussions/84">Historical Note: How OVM_ETH works (as of Optimism v0.5.0) #84</a>，应该是 Optimism 早对 非交互式欺诈证明的实现，是通过在 L1 构造虚拟环境 OVM 来重新执行交易 (合约代码使用修改版本的 solidity 编译器生成，原生 OPCODE 被编译为通过 OVM 获取执行上下文)，而虚拟环境将 balance 封装为 ERC20。参考 <a href="#欺诈证明">欺诈证明</a>，Optimism 目前已暂停 非交互式欺诈证明，但为了保持兼容，在 L2 仍以 ERC20 方式存储账户的 balance</p>
<blockquote>
<p>The original motivation for putting L2 ETH in an ERC20 token has to do with the 'containerized' approach behind how the old OVM worked.</p>
</blockquote>
<h2>EVM 特殊处理</h2>
<h3>EVM 上下文</h3>
<pre><code class="language-go">// l2geth/core/evm.go

// NewEVMContext creates a new context for use in the EVM.
func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author *common.Address) vm.Context {
    // If we don&#039;t have an explicit author (i.e. not mining), extract from the header
    var beneficiary common.Address
    if author == nil {
        beneficiary, _ = chain.Engine().Author(header) // Ignore error, we&#039;re past header validation
    } else {
        beneficiary = *author
    }

    if rcfg.UsingOVM {
        // When using the OVM, we must:
        // - Set the Time to be the msg.L1Timestamp
        return vm.Context{
            CanTransfer:   CanTransfer,
            Transfer:      Transfer,
            GetHash:       GetHashFn(header, chain),

            // 如果是 L2 -&gt; L2 消息，则未修改
            // 如果是 L1 -&gt; L2 消息，则可能是 msg.sender 的 alias (如果非 EOA)
            Origin:        msg.From(),

            // 收益地址固定为  0x4200....0011
            Coinbase:      dump.OvmFeeWallet, // Coinbase is the fee vault.

            BlockNumber:   new(big.Int).Set(header.Number),

            // 使得 opTimestamp 取值是 L1Timestamp
            Time:          new(big.Int).SetUint64(msg.L1Timestamp()),

            // 固定为 0
            Difficulty:    new(big.Int), // Difficulty always returns zero.

            GasLimit:      header.GasLimit,
            GasPrice:      new(big.Int).Set(msg.GasPrice()),

            // 设置 l1BlockNumber
            L1BlockNumber: msg.L1BlockNumber(),
        }
    }
}</code></pre>
<p>参考 <a href="https://community.optimism.io/docs/developers/build/differences/#">Differences between Ethereum and Optimism</a></p>
<p>go-ethereum 在将交易提交给 EVM 执行前，需要创建 <code>vm.Context</code>；<code>Sequencer</code> 做了如下特殊处理：</p>
<ul>
<li>Time 设置为 msg.L1Timestamp() (参考 <a href="#区块时间">区块时间</a>)</li>
<li>Origin 设置为 msg.From() (参考 <a href="#l1-l2">L1-&gt;L2</a>)</li>
<li>Coinbase 设置为固定地址 OvmFeeWallet (0x4200...0011)</li>
<li>Difficulty 设置为 0</li>
</ul>
<p>另外，<code>Sequencer</code> 在 <code>vm.Context</code> 中新增了字段 <code>L1BlockNumber</code>；如果 L2 合约需要获取最近的 L1 区块高度，可以查询 预部署合约 <code>Lib_PredeployAddresses.L1_BLOCK_NUMBER</code> (0x4200...0013)</p>
<h3>合约部署</h3>
<p>Optimism 通过 预部署合约 管理在 L2 上部署合约的权限：OVM_DeployerWhitelist.sol (0x4200...0002)</p>
<p>针对 <code>Create</code> / <code>Create2</code> 两个操作码，EVM 在实际处理前，会从 白名单合约 中查询调用者的权限</p>
<pre><code class="language-go">// l2geth/core/vm/evm.go

// create creates a new contract using code as deployment code.
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address) ([]byte, common.Address, uint64, error) {
    if rcfg.UsingOVM {
        if !evm.AddressWhitelisted(caller.Address()) {
            return ret, common.Address{}, gas, errExecutionReverted
        }
    }

  // ...
}</code></pre>
<h2>费用</h2>
<p>相关文档</p>
<ul>
<li><a href="https://community.optimism.io/docs/developers/build/transaction-fees/">Transaction fees on L2</a></li>
<li><a href="https://community.optimism.io/docs/developers/bridge/messaging/#fees-for-sending-data-between-l1-and-l2">Sending data between L1 and L2 #Fees</a></li>
</ul>
<h3>L1-&gt;L2 交易</h3>
<h4>L2 费用</h4>
<p>参考 <a href="#l2-交易执行">L1-&gt;L2：L2 交易执行</a>：交易在 L2 被中继时，<code>value</code> 和 <code>gasPrice</code> 字段为 0，即不消耗费用</p>
<p>在 <a href="https://github.com/ethereum-optimism/optimistic-specs/discussions/32">How should we pay gas for deposits? #32</a> 中，Optimism 开发团队讨论了另一种方案：在 L2 收取费用；目前没有定稿，偏向已有方案</p>
<h4>L1 费用</h4>
<p>参考上文，用户不需要为 L1-&gt;L2 交易 支付 L2 执行费用；这引起了下面的问题：</p>
<p>风险：攻击者在提交 L1-&gt;L2 交易时，将参数 <code>_gasLimit</code> 设置得很大，然后在 L2 合约中进行耗时操作，导致 Sequencer 性能下降</p>
<p>防范：Optimism 设置了阈值 <code>enqueueL2GasPrepaid</code>，如果提交 L1-&gt;L2 交易时，参数 <code>_gasLimit</code> 超过阈值，那么合约将根据 超出大小 按比例燃烧 gas；</p>
<p>参数：</p>
<ul>
<li><code>enqueueL2GasPrepaid</code>
<ul>
<li>配置为 1,920,000</li>
</ul>
</li>
<li><code>l2GasDiscountDivisor</code>
<ul>
<li>配置为 32</li>
</ul>
</li>
</ul>
<pre><code class="language-js">// packages/contracts/contracts/L1/rollup/CanonicalTransactionChain.sol

contract CanonicalTransactionChain is ICanonicalTransactionChain, Lib_AddressResolver {
    function enqueue(address _target, uint256 _gasLimit, bytes memory _data) external {
        // 如果 参数 超过 阈值
        if (_gasLimit &gt; enqueueL2GasPrepaid) {
            // 根据 超出大小，计算燃烧的 gas
            uint256 gasToConsume = (_gasLimit - enqueueL2GasPrepaid) / l2GasDiscountDivisor;
            uint256 startingGas = gasleft();

            // 燃烧 gas (通过空转)
            uint256 i;
            while (startingGas - gasleft() &lt; gasToConsume) {
                i++;
            }
        }

        // ...
    }
}</code></pre>
<h3>L2 交易</h3>
<p><code>total_fee = l2_fee + l1_data_fee</code></p>
<p>在 L2 发起的交易，除了需要支付在 L2 本身的费用之外，还需要支付其在 L1 的上链费用。</p>
<p>注意：L2-&gt;L1 跨链消息的费用，与 L2-&gt;L2 交易费用一致。因为用户需要自己在 L1 调用中继消息，因此中继成本由用户承担 (参考 <a href="#比较-arbitrum">消息传递：比较 Arbitrum</a>)</p>
<h4>L2 费用</h4>
<p><code>l2_fee = l2_gas_price * gas</code></p>
<p>其中：<code>l2_gas_price</code> 表示 L2 当前的 <code>gasPrice</code></p>
<p>根据 <a href="https://community.optimism.io/docs/developers/build/transaction-fees/#responding-to-gas-price-updates">Transaction fees on L2: Responding to gas price updates</a>，<code>l2_gas_price</code> 会根据拥塞情况动态变化，但我未没找到实现 (Sequencer 确实实现了私有 API <code>rollup_setL2GasPrice()</code>，但没有调用代码)；我在 Optimism L2 链上多次查询该字段的取值，都是 1,000,000 (即 0.001 Gwei)。因此动态 <code>gasPrice</code> 应该是开发中..</p>
<p>目前，<code>eth_gasPrice</code> API 返回的 <code>gasPrice</code> 都是 0.001 Gwei</p>
<h4>L1 费用</h4>
<p><code>l1_data_fee = l1_gas_price * (tx_data_gas + fixed_overhead) * dynamic_overhead</code></p>
<p>字段：</p>
<ul>
<li>l1_gas_price
<ul>
<li>L1 平均 <code>gasPrice</code></li>
</ul>
</li>
<li>tx_data_gas
<ul>
<li><code>count_zero_bytes(tx_data) * 4 + count_non_zero_bytes(tx_data) * 16</code></li>
</ul>
</li>
<li>fixed_overhead
<ul>
<li>固定 gas，配置为 2100</li>
</ul>
</li>
<li>dynamic_overhead
<ul>
<li>放大系数，配置为 1.24</li>
</ul>
</li>
</ul>
<p>其中，<code>l1_gas_price</code> 来自 gas-oracle 服务，它定期统计 L1 平均 <code>gasPrice</code> ，更新至 L2 预部署合约 OVM_GasPriceOracle (0x4200...000F)</p>
<p>计算 L1 上链费用：</p>
<pre><code class="language-js">// l2geth/rollup/fees/rollup_fee.go

func CalculateL1MsgFee(msg Message, state StateDB, gpo *common.Address) (*big.Int, error) {
    // 将交易序列化为 calldata
    tx := asTransaction(msg)
    raw, err := rlpEncode(tx)

    // 读取 GasPriceOracle 中的参数：L1 平均 gasPrice，固定 gas，放大系数
    l1GasPrice, overhead, scalar := readGPOStorageSlots(*gpo, state)

    // 计算 L1 费用
    l1Fee := CalculateL1Fee(raw, overhead, l1GasPrice, scalar)
    return l1Fee, nil
}

func CalculateL1Fee(data []byte, overhead, l1GasPrice *big.Int, scalar *big.Float) *big.Int {
  // 计算 L1 gas
    l1GasUsed := CalculateL1GasUsed(data, overhead)

  // 乘以 gasPrice
    l1Fee := new(big.Int).Mul(l1GasUsed, l1GasPrice)

    // 乘以放大系数 scalar (1.24)
    return mulByFloat(l1Fee, scalar)
}

func CalculateL1GasUsed(data []byte, overhead *big.Int) *big.Int {
    zeroes, ones := zeroesAndOnes(data)

    // 空字节消耗的 gas
    zeroesGas := zeroes * params.TxDataZeroGas

    // 非空字节消耗的 gas
    onesGas := (ones + 68) * params.TxDataNonZeroGasEIP2028

    // 预期 gas
    l1Gas := new(big.Int).SetUint64(zeroesGas + onesGas)

    // 加上 固定 gas
    return new(big.Int).Add(l1Gas, overhead)
}</code></pre>
<p>在执行交易前预先扣除 L1 费用：</p>
<pre><code class="language-go">// l2geth/core/state_transition.go

func (st *StateTransition) buyGas() error {
    mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice)

    if rcfg.UsingOVM {
        // 如果是 L2 -&gt; L2 消息，需要加上上链费用 l1Fee
        // Only charge the L1 fee for QueueOrigin sequencer transactions
        if st.msg.QueueOrigin() == types.QueueOriginSequencer {
            mgval = mgval.Add(mgval, st.l1Fee)
            if st.msg.CheckNonce() {
                log.Debug(&quot;Adding L1 fee&quot;, &quot;l1-fee&quot;, st.l1Fee)
            }
        }
    }

    // ...
}</code></pre>
<h3>Sequencer</h3>
<p>根据 <a href="#evm-上下文">EVM 上下文</a>，目前 L2 上所有交易消耗的 gas，都由矿工 0x4200...0011 获得</p>
<p>这个地址是个非常简单的智能合约 <code>OVM_SequencerFeeVault</code>，它实现了一个公开函数 <code>withdraw()</code>：通过 StandardBridge 将 ETH 取出到 L1 上的固定地址 <code>l1FeeWallet</code>；<code>L1FeeWallet</code> 这个地址没有什么特殊逻辑</p>
<pre><code class="language-js">// packages/contracts/contracts/L2/predeploys/OVM_SequencerFeeVault.sol
contract OVM_SequencerFeeVault {
    function withdraw() public {
        L2StandardBridge(Lib_PredeployAddresses.L2_STANDARD_BRIDGE).withdrawTo(
            Lib_PredeployAddresses.OVM_ETH,
            l1FeeWallet,
            address(this).balance,
            0,
            bytes(&quot;&quot;)
        );
    }
}</code></pre>
<h2>经济模型</h2>
<ul>
<li><a href="https://barnabe.substack.com/p/understanding-rollup-economics-from">Understanding rollup economics from first principles</a></li>
</ul>
<p>TODO 没找到什么资料；从 Optimism 源码看不出来...</p>
<h3>两难</h3>
<p>Arbitrum 团队关于 Asserter 和 Checker 的思考</p>
<ul>
<li>
<p><a href="https://medium.com/offchainlabs/the-cheater-checking-problem-why-the-verifiers-dilemma-is-harder-than-you-think-9c7156505ca1">The Cheater Checking Problem: Why the Verifier’s Dilemma is Harder Than You Think</a></p>
<ul>
<li>
<p>the Asserter can cheat randomly, with probability of cheating less than C/(R+L), and a rational Checker will never check, so the Asserter will never get caught cheating.</p>
</li>
<li>
<p>Simply increasing the number of verifiers is very helpful in preventing bribery attacks; however, it has rather negative effects on solving the verifier’s laziness.</p>
</li>
</ul>
</li>
<li>
<p><a href="https://medium.com/offchainlabs/cheater-checking-how-attention-challenges-solve-the-verifiers-dilemma-681a92d9948e">Cheater Checking: How attention challenges solve the verifier’s dilemma</a></p>
<ul>
<li>Attention challenges</li>
<li>The key observation is that as long as P*A &gt; C, then checking is the best strategy, no matter what X (the probability of cheating) is.</li>
</ul>
</li>
</ul>
<h3>激励</h3>
<p>TODO</p>
]]></content:encoded>
					
					<wfw:commentRss>https://godorz.info/2022/04/optimism-notes/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>零知识证明 &#8211; PLONK 学习笔记</title>
		<link>https://godorz.info/2022/04/plonk-notes/</link>
					<comments>https://godorz.info/2022/04/plonk-notes/#respond</comments>
		
		<dc:creator><![CDATA[ripwu]]></dc:creator>
		<pubDate>Thu, 28 Apr 2022 02:53:11 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[ZKP]]></category>
		<guid isPermaLink="false">https://godorz.info/?p=1828</guid>

					<description><![CDATA[之前学习 PLONK 的笔记，主要来自公众号 blocksight 3 - 多项式承诺 有限域 \mathbb{F}_{p} \triangleq \{0, 1, 2, ... p-1\} 群 \mathbb{G} \triangleq \{g^0, g^1, g^2, ... g^{(p-1)}\}，其中 g 为 生成元 在有限域 \mathbb{F}_{p} 上的 d 阶多项式 f(x) = a_0 + a_1x + a_2x^2 + ... + a_dx^d 其中，d 远远小于 p，比如 d 为 2^{20} 而 p 为 2^{512} -- P 拥有多项式，即他知道系数 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>之前学习 PLONK 的笔记，主要来自公众号 blocksight</p>
<pre><code class="language-c"></code></pre>
<h2>3 - 多项式承诺</h2>
<p>有限域 <code class="katex-inline">\mathbb{F}_{p} \triangleq \{0, 1, 2, ... p-1\}</code></p>
<p>群 <code class="katex-inline">\mathbb{G} \triangleq \{g^0, g^1, g^2, ... g^{(p-1)}\}</code>，其中 <code class="katex-inline">g</code> 为 生成元</p>
<p>在有限域 <code class="katex-inline">\mathbb{F}_{p}</code> 上的 <code class="katex-inline">d</code> 阶多项式 <code class="katex-inline">f(x) = a_0 + a_1x + a_2x^2 + ... + a_dx^d</code></p>
<p>其中，<code class="katex-inline">d</code> 远远小于 <code class="katex-inline">p</code>，比如 <code class="katex-inline">d</code> 为 <code class="katex-inline">2^{20}</code> 而 <code class="katex-inline">p</code> 为 <code class="katex-inline">2^{512}</code></p>
<p>--</p>
<p>P 拥有多项式，即他知道系数 <code class="katex-inline">\{a_0, a_1, a_2, ... a_d\}</code></p>
<p>V 做验证</p>
<h3>多项式承诺</h3>
<p>P 需要向 V 承诺他知道这个多项式，承诺过程：V 给出 在 <code class="katex-inline">\mathbb{F}_{p}</code> 域内的随机值 <code class="katex-inline">r</code>，P 给出多项式在这个点的取值 <code class="katex-inline">f(r)</code></p>
<p>但是，为了避免 P 作弊，V 不能直接给他 <code class="katex-inline">r</code> 明文；而是在加密空间中交互，推导如下：</p>
<p><strong>关键</strong> 对多项式 <code class="katex-inline">f(x) = a_0 + a_1x + a_2x^2 + ... + a_dx^d</code> 左右求指数，即：</p>
<pre><code class="language-katex">\begin{aligned}
g^{f(r)} =&amp; g^{a_0 + a_1r + a_2r^2 + ... + a_dr^d} \\
=&amp; g^{a_0} \cdot (g^r)^{a_1} \cdot (g^{r^2})^{a_2} \cdot ... \cdot (g^{r^d})^{a_d}
\end{aligned}</code></pre>
<p>即：</p>
<p>V 给出的不是 <code class="katex-inline">r</code>，而是加密空间的 <code class="katex-inline">\{g, g^r, g^{r^2}, ... g^{r^d}\}</code>，称为公共参数</p>
<p>P 回复的承诺不是 <code class="katex-inline">f(r)</code>，而是根据系数 <code class="katex-inline">\{a_0, a_1, a_2, ..., a_d\}</code> 计算出的 <code class="katex-inline">g^{f(r)}</code></p>
<p>分析：</p>
<ul>
<li>承诺拥有 binding 的性质，这是对 V 的保护
<ul>
<li>因为 离散对数 不可解 (准确说法：没有 多项式时间 解法)，所以</li>
<li>P 无法通过 <code class="katex-inline">\{g, g^r, g^{r^2}, ... g^{r^d}\}</code> 解出 <code class="katex-inline">r</code></li>
<li><code class="katex-inline">g^{f(r)}</code> 固定了 <code class="katex-inline">f(r)</code>， 而 <code class="katex-inline">f(r)</code> 进一步固定了 <code class="katex-inline">f</code></li>
<li>换句话说，随机数 <code class="katex-inline">r</code> 对 P 不可见</li>
<li>所以 P 无法伪造承诺</li>
<li>V 可以事后要求 P 打开承诺，然后自行根据参数 <code class="katex-inline">\{g, g^r, g^{r^2}, ... g^{r^d}\}</code>，计算 <code class="katex-inline">g'^{f(r)}</code> 并与 <code class="katex-inline">g^{f(r)}</code> 比较</li>
</ul>
</li>
</ul>
<p>补充：</p>
<p>离散对数 不可解：给出 <code class="katex-inline">g^a</code>， 无法反解 <code class="katex-inline">a</code></p>
<p>-</p>
<ul>
<li>承诺拥有 hiding 的性质，这是对 P 的保护
<ul>
<li>因为 P 回复的承诺为 <code class="katex-inline">g^{f(r)}</code>， 所以 V 无法通过它反推 <code class="katex-inline">\{a_0, a_1, a_2, ..., a_d\}</code></li>
<li>换句话说，V 无法从 承诺 反推多项式</li>
</ul>
</li>
</ul>
<p>--</p>
<h3>部分打开</h3>
<p>V 给出随机数 <code class="katex-inline">z</code>， P 返回多项式计算结果 <code class="katex-inline">s</code>， V 表示怀疑</p>
<p>问题：P 如何向 V 证明 <code class="katex-inline">f(z) = s</code></p>
<p>转换：</p>
<p>证明 <code class="katex-inline">f(z) = s</code>， 等于证明 <code class="katex-inline">f(z) - s = 0</code>， 即 <code class="katex-inline">z</code> 是多项式 <code class="katex-inline">f'(x)</code> 的一个根，其中 <code class="katex-inline">f'(x) = f(x) - s</code></p>
<p>根据 <a href="Factor_theorem">因式定理</a> 可知：<code class="katex-inline">x -z</code> 是 多项式 <code class="katex-inline">f'(x)</code> 的因子</p>
<p>将第二因子记为 <code class="katex-inline">t(x)</code>， 则 <code class="katex-inline">f'(x) = t(x) \cdot (x - z)</code>， 又因为 <code class="katex-inline">f'(x) = f(x) - s</code>， 所以：</p>
<pre><code class="language-katex">f(x) - s = t(x) \cdot (x - z)</code></pre>
<p>P 可以 多项式长除法 (long division)，计算出 <code class="katex-inline">t(x)</code></p>
<p>我们将 <code class="katex-inline">t(x)</code> 和 <code class="katex-inline">x - z</code> 分别看作新的多项式，则公式右边为 两个多项式的乘法，即 二次方程组</p>
<p>显然，P 不能直接将 二次方程组 给 V，否则 V 可以推导出左边的原始多项式</p>
<p>利用：Schwartz–Zippel lemma</p>
<p>转换：给出一个随机点 <code class="katex-inline">r</code>， 只要能承诺这个随机点 <code class="katex-inline">r</code> 上 <code class="katex-inline">t(x) \cdot (x - z)</code> 的取值，我们就承诺了整个 二次式</p>
<p>换句话说，要承诺</p>
<pre><code class="language-katex">f(x) - s = t(x) \cdot (x - z)</code></pre>
<p>只需承诺</p>
<pre><code class="language-katex">f(r) - s = t(r) \cdot (r - z)</code></pre>
<p>问题：如何做 <code class="katex-inline">r</code> 在 多项式乘法 的承诺</p>
<p>解决：</p>
<p>利用椭圆曲线 pairing 的两个性质，来验证乘法关系：</p>
<p>(1) <code class="katex-inline">e(g_1, g_2) = g_T</code></p>
<p>(2) <code class="katex-inline">e({g_1}^a, {g_2^b}) = {g_T}^{a \cdot b}</code></p>
<p>其中，</p>
<p><code class="katex-inline">e</code> 是 椭圆曲线 的 pairing，是个 双线性映射公式，且这个公式公开 (参考 <a href="https://mp.weixin.qq.com/s/ymElad5WmXAbvgklxVKL-g">blocksight: 区块链中的数学（六十二）双线性映射</a>)</p>
<p><code class="katex-inline">G_1</code>，<code class="katex-inline">G_2</code>， <code class="katex-inline">G_T</code> 是乘法循环群，<code class="katex-inline">g_1</code>，<code class="katex-inline">g_2</code>，<code class="katex-inline">g_T</code> 分别是它们的 生成元</p>
<p>过程：</p>
<p>二次方程组 进入加密空间，应用性质 (1) 和 (2)，得到：</p>
<pre><code class="language-katex">\begin{aligned}
&amp; \ f(r) - s = t(r) \cdot (r - z) &amp; \\
\Rightarrow &amp; \ {g_T}^{f(r) - s} = {g_T}^{t(r) \cdot (r - z)} &amp; \\
\Rightarrow &amp; \ {e(g_1, g_2)}^{f(r) - s} = {e(g_1, g_2)}^{t(r) \cdot (r - z)} &amp; 应用 (1)\\
\Rightarrow &amp; \ {e(\frac{{g_1}^{f(r)}}{{g_1}^s}, g2)} = {e({g_1}^{t(r)}, \frac{{g_2}^r}{{g_2}^z})} &amp; 应用 (2)
\end{aligned}</code></pre>
<p>其中，<code class="katex-inline">g_1</code> 和 <code class="katex-inline">g_2</code> 是公共参数，P 和 V 都知道</p>
<p>V 必须有 5 个参数 <code class="katex-inline">{g_1}^{f(r)}</code>，<code class="katex-inline">{g_1}^s</code>，<code class="katex-inline">{g_1}^{t(r)}</code>，<code class="katex-inline">{g_2}^r</code>，<code class="katex-inline">{g_2}^z</code>， 才能验证参数</p>
<p>接下来，逐个看这 5 个 参数</p>
<ul>
<li><code class="katex-inline">{g_1}^{f(r)}</code>，<code class="katex-inline">{g_1}^{t(r)}</code>
<ul>
<li><code class="katex-inline">r</code> 是 setup 阶段随机数，对 P 和 V 保密</li>
<li>P 计算出来给 V</li>
</ul>
</li>
<li><code class="katex-inline">{g_1}^s</code>
<ul>
<li>P 将 <code class="katex-inline">s</code> 明文发给 V，V 再计算 <code class="katex-inline">{g_1}^s</code></li>
</ul>
</li>
<li><code class="katex-inline">{g_2}^r</code>
<ul>
<li><code class="katex-inline">{g_2}^r</code> 是公共参数，对 P 和 V 都已知</li>
<li>由于 离散对数 不可解，任何人无法知道秘密 <code class="katex-inline">r</code></li>
</ul>
</li>
<li><code class="katex-inline">{g_2}^z</code>
<ul>
<li><code class="katex-inline">g_2</code> 是公共参数，而 <code class="katex-inline">z</code> 是 V 选取的随机数，所以 V 可以计算 <code class="katex-inline">{g_2}^z</code></li>
</ul>
</li>
</ul>
<p>经过上面的处理，V 最终有了这 5 个参数，所以它可以验证整个式子</p>
<p>其中，关键参数有：</p>
<ul>
<li><code class="katex-inline">{g_1}^{f(r)}</code>
<ul>
<li>承诺</li>
<li>commitment</li>
</ul>
</li>
<li><code class="katex-inline">s</code>
<ul>
<li>声明</li>
<li><code class="katex-inline">f(x)</code> 在 <code class="katex-inline">z</code> 上的取值</li>
</ul>
</li>
<li><code class="katex-inline">{g_1}^{t(r)}</code>
<ul>
<li>证明</li>
<li>witness</li>
</ul>
</li>
</ul>
<p>--</p>
<p>以上就是 P 如何向 V 证明：<code class="katex-inline">f(z) = s</code> 的过程，</p>
<h3>setup</h3>
<p>概括的说，多项式承诺分为两个阶段：承诺 和 打开</p>
<p>在 承诺 阶段，P 向 V 返回 <code class="katex-inline">g^{f(r)}</code></p>
<p>在 打开 阶段，P 向 V 返回 <code class="katex-inline">g^{t(r)}</code></p>
<p>注意：承诺 阶段的随机数 <code class="katex-inline">r</code>， 与 打开 阶段的随机数 <code class="katex-inline">r</code>， 是同一个</p>
<p>这就引出了 setup 的概念</p>
<p>--</p>
<p>在多项式承诺系统开始前，需要 可信的第三方 通过 setup 构建任何人都不知道的随机数 <code class="katex-inline">r</code>， 并通过它计算参数：</p>
<pre><code class="language-katex">\begin{cases}
\{g_1, {g_1}^r, {g_1}^{r^2}, ... {g_1}^{r^d}\} \\
\{g_2, {g_2}^r\}
\end{cases}</code></pre>
<p>这两组参数称为 公开参数 public params，对 P 和 V 都公开</p>
<p>其中，</p>
<p>P 需要 <code class="katex-inline">\{g_1, {g_1}^r, {g_1}^{r^2}, ... {g_1}^{r^d}\}</code> 来计算 承诺 和 witness</p>
<p>V 需要的是 <code class="katex-inline">\{g_2, {g_2}^r\}</code></p>
<p>--</p>
<p>引入 可信第三方 来 setup 这些参数，而不由 V 直接选择这些参数，是因为在区块链的应用场景中，P 和 V 的角色可以是动态的，而非一成不变的；比如一个角色今天可能是 V，明天可能是 P</p>
<p>所以，我们要求任何人都不能知道 <code class="katex-inline">r</code>， 否则可以作弊</p>
<p>更进一步，在 setup 构建公共参数后，第三方必须销毁随机数 <code class="katex-inline">r</code></p>
<p>一种方式：MPC 多方构建</p>
<p>--</p>
<p>PLONK 中 setup 是 可更新 updatable 的</p>
<p>首先，第三方 <code class="katex-inline">{P_1}</code> 通过随机数 <code class="katex-inline">r_1</code> 生成公共参数：</p>
<pre><code class="language-katex">{P_1} : \{g_1, {g_1}^{r_1}, {g_1}^{{r_1}^2}, ... {g_1}^{{r_1}^d}\}</code></pre>
<p>然后，第三方 <code class="katex-inline">P_2</code> 不信任系统，它可以通过 <code class="katex-inline">r_2</code> 更新公共参数：</p>
<pre><code class="language-katex">\begin{cases}
{P_1} : \{g_1, {g_1}^{r_1}, {g_1}^{{r_1}^2}, ... {g_1}^{{r_1}^d}\} \\
{P_2} : \{g_1, {{g_1}^{r_1}}^{r_2}, {{g_1}^{{r_1}^2}}^{{r_2}^2}, ... {{g_1}^{{r_1}^d}}^{{r_2}^d}\} = \{g_1, {g_1}^{r_1 \cdot r_2}, {g_1}^{{r_1 \cdot r_2}^2}, ... {g_1}^{{r_1 \cdot r_2}^d}\}
\end{cases}</code></pre>
<p>注意，<code class="katex-inline">{P_1}</code> 不知道 <code class="katex-inline">r_2</code>，<code class="katex-inline">{P_2}</code> 不知道 <code class="katex-inline">r_1</code></p>
<p>因此，任何一方都可以更新公告参数，使得自己和其他人都无法知道随机数 <code class="katex-inline">r_1 \cdot r_2 \cdot ...</code></p>
<h2>4 - SRS 与门电路</h2>
<p>Kate 承诺：用多项式在某一个随机点上的值，来决定 <code class="katex-inline">f(x)</code></p>
<p>--</p>
<p>电路可满足问题 Circuit Satisfiability Problem</p>
<p>给定 <code class="katex-inline">x</code> 和 <code class="katex-inline">y</code>， 问是否存在 <code class="katex-inline">w</code> 满足 <code class="katex-inline">C(x, w) = y</code></p>
<p>其中: <code class="katex-inline">x</code> 已知输入，<code class="katex-inline">y</code> 已知输出，<code class="katex-inline">w</code> 未知输入</p>
<p>零知识证明：P 找到了这个解 <code class="katex-inline">w</code>。他要向 V 证明自己知道这个解，但不暴露 <code class="katex-inline">w</code></p>
<p>例子：</p>
<p>现有加法电路 <code class="katex-inline">a_1 + b_1 = c_1</code>， 假设 <code class="katex-inline">a_1</code> 和 <code class="katex-inline">c_1</code> 已知，分别为 1 和 3，问是否存在 <code class="katex-inline">b_1</code> 满足这个加法电路</p>
<p>表达为约束方程，即：</p>
<pre><code class="language-katex">\begin{cases}
a_1 + b_1 = c_1 \\
a_1 = 1 \\
c_1 = 3 \\
\end{cases}</code></pre>
<p>可以看到，在上面的例子中，一个门存在 3 个约束</p>
<p>问题：如何实现一个门有且只有一个约束</p>
<p>解答：引入 常数门</p>
<p>这样使得约束方程为</p>
<pre><code class="language-katex">\begin{cases}
a_1 + b_1 = c_1 \\
a_1 = c_0 \\
c_0 = 1 \\
\end{cases}</code></pre>
<p>其中，<code class="katex-inline">c_0</code> 为假想的门，它的约束为 <code class="katex-inline">c_0 = 1</code> (忽略左右输入 <code class="katex-inline">a_0</code> 和 <code class="katex-inline">b_0</code> 的约束)</p>
<p>因为引入了 常数门，系统中一共存在 5 种情况的约束：</p>
<ul>
<li><code class="katex-inline">a + b = c</code> 加法约束</li>
<li><code class="katex-inline">a \cdot b = c</code> 乘法约束</li>
<li><code class="katex-inline">a = constant</code> 已知输入 <code class="katex-inline">x</code> 或 已知输出 <code class="katex-inline">y</code></li>
<li>或 <code class="katex-inline">b = constant</code> -</li>
<li>或 <code class="katex-inline">c = constant</code> -</li>
</ul>
<p>这样改造后，一套电路等价于一个约束系统，每个门对应一个约束，且每个约束必然是 5 种约束之一</p>
<p>问题转为：如何用 一个公式 表达 上面 5 个公式</p>
<p>回答：</p>
<pre><code class="language-katex">(Q_L) a+ (Q_R) b + (Q_O) c + (Q_M) ab + Q_C = 0</code></pre>
<p>通过恰当地选择下面 5 个系数，公式就可以表达上面 5 种约束中的一种</p>
<ul>
<li><code class="katex-inline">Q_L</code> 加法门左输入系数</li>
<li><code class="katex-inline">Q_R</code> 加法门右输入系数</li>
<li><code class="katex-inline">Q_O</code> 输出系数</li>
<li><code class="katex-inline">Q_M</code> 乘法门系数</li>
<li><code class="katex-inline">Q_C</code> 常数</li>
</ul>
<p>--</p>
<p>如果有 <code class="katex-inline">n</code> 个门，那么存在 <code class="katex-inline">3n</code> 个变量</p>
<pre><code class="language-katex">\begin{bmatrix}
a_1 &amp; a_2 &amp; a_3 &amp; ... &amp; a_n \\
b_1 &amp; b_2 &amp; b_3 &amp; ... &amp; b_n \\
c_1 &amp; c_2 &amp; c_3 &amp; ... &amp; c_n \\
\end{bmatrix}</code></pre>
<p>这些变量中，有一系列的约束方程；这些约束方程，可以分为两大类，一类是 门约束，一类是 复制约束：</p>
<p>门约束 可以归结为 <code class="katex-inline">(Q_L) a + (Q_R) b + (Q_O) c + (Q_M) ab + Q_C = 0</code>， 每个门 有且只有 一个 门约束</p>
<p>复制约束 一定是形式 <code class="katex-inline">a_3 = c_5</code>；即在 <code class="katex-inline">3n</code> 个变量中，某一个变量等于另一个变量</p>
<h2>5 - 置换与复制约束</h2>
<h3>门约束</h3>
<p>zk-SNARK 电路有输入和输出，问给定 <code class="katex-inline">x</code> 和 <code class="katex-inline">y</code>， 问是否存在 <code class="katex-inline">w</code> 满足 <code class="katex-inline">C(x, w) = y</code></p>
<p>这里有个反直觉的地方：为什么输出 <code class="katex-inline">y</code> 是已知的，还没计算又怎么知道输出呢？</p>
<p>例子：已知一个图，问是否存在三色染色的解。在这个问题中，图 就是已知输出</p>
<p>在零知识证明中，P 求出了这个数学方程的解 <code class="katex-inline">w</code>， 在不暴露 <code class="katex-inline">w</code> 的情况下，向 V 证明自己求出了解</p>
<p>--</p>
<p>回忆 <a href="#4---srs-与门电路">#4 - SRS 与门电路</a>，我们引入 常数门 后，一个电路系统有一套约束系统，每个门有且仅有一个约束条件</p>
<p>假设电路有 <code class="katex-inline">n</code> 个门，则他有 <code class="katex-inline">n</code> 个约束条件；我们以 <code class="katex-inline">C</code> 表示一个约束，则它可以表示为 <code class="katex-inline">C_i(a_i, b_i, c_i) = 0</code></p>
<p>每个约束条件为一个方程，共 <code class="katex-inline">n</code> 个约束条件，形成方程组：</p>
<pre><code class="language-katex">\begin{cases}
C_1(a_1, b_1, c_1) = 0 \\
C_2(a_2, b_2, c_2) = 0 \\
C_3(a_3, b_3, c_3) = 0 \\
... \\
C_n(a_n, b_n, c_n) = 0 \\
\end{cases}</code></pre>
<p>注意：这个 方程组 等价于 原来的问题 <code class="katex-inline">C(x, w) = y</code></p>
<p>每个方程可以表达为 统一格式 (但需要正确的选择系数)：</p>
<pre><code class="language-katex">({Q_L}_i) a_i + ({Q_R}_i) b_i + ({Q_O}_i) c_i + ({Q_M}_i) a_i b_i + {Q_C}_i = 0</code></pre>
<p>所以，方程组 可以表达为：</p>
<pre><code class="language-katex">\begin{cases}
({Q_L}_0) a_0 + ({Q_R}_0) b_0 + ({Q_O}_0) c_0 + ({Q_M}_0) a_0 b_0 + {Q_C}_0 = 0 \\
({Q_L}_1) a_1 + ({Q_R}_1) b_1 + ({Q_O}_1) c_1 + ({Q_M}_1) a_1 b_1 + {Q_C}_1 = 0 \\
({Q_L}_2) a_2 + ({Q_R}_2) b_2 + ({Q_O}_2) c_2 + ({Q_M}_2) a_2 b_2 + {Q_C}_2 = 0 \\
... \\
({Q_L}_n) a_n + ({Q_R}_n) b_n + ({Q_O}_n) c_n + ({Q_M}_n) a_n b_n + {Q_C}_n = 0 \\
\end{cases}</code></pre>
<p>如果按 每个门有一个方程组 的思路，横向看待方程组，每个方程有 8 个符号：输入输出 <code class="katex-inline">a</code>，<code class="katex-inline">b</code>，<code class="katex-inline">c</code> 加上 5 个选择系数</p>
<p><strong>关键</strong> 切换视角，纵向看待 8 个符号，可以得到 8 个多项式</p>
<p>以 <code class="katex-inline">Q_L</code> 为例，它有 <code class="katex-inline">n</code> 个点，且在点 <code class="katex-inline">i</code> 上的取值为 <code class="katex-inline">{Q_L}_i</code></p>
<p>通过 拉格朗日插值，我们可以得到这些 点 和 取值 表示的 <code class="katex-inline">n-1</code> 阶的多项式</p>
<p>更进一步的，我们可以将这 8 个多项式，表达为：</p>
<pre><code class="language-katex">{Q_L}(x) \cdot a(x) + {Q_R}(x) \cdot b(x) + {Q_O}(x) \cdot c(x) + {Q_M}(x) \cdot a(x) \cdot b(x) + {Q_C}(x) = 0 \\
where \quad x \in \{x_1, x_2, ..., x_n\}</code></pre>
<p>注意：这个多项式只在 <code class="katex-inline">\{x_1, x_2, ..., x_n\}</code> 这些点上取值为 0</p>
<p>问题：<code class="katex-inline">x</code> 取值不是 <code class="katex-inline">\{1, 2, ..., n\}</code> 吗，怎么成了 <code class="katex-inline">\{x_1, x_2, ..., x_n\}</code></p>
<p>理解：每个门的下标记为 <code class="katex-inline">\{1, 2, ..., n\}</code> 只是为了理解方便，实际上必须是在有限域 <code class="katex-inline">\mathbb{F}_{p}</code> 上，精心选择的 <code class="katex-inline">n</code> 个点 <code class="katex-inline">\{x_1, x_2, ..., x_n\}</code></p>
<p>参考 <a href="#复制约束">复制约束</a>：<code class="katex-inline">\{x_1, x_2, ..., x_n\}</code> 需要满足一定的要求，实际取值为 <code class="katex-inline">\{\omega, \omega^2, ..., \omega^n = 1\}</code></p>
<p><strong>关键</strong> 再次转换</p>
<p>转为多项式</p>
<pre><code class="language-katex">F(x) = {Q_L}(x) \cdot a(x) + {Q_R}(x) \cdot b(x) + {Q_O}(x) \cdot c(x) + {Q_M}(x) \cdot a(x) \cdot b(x) + {Q_C}(x) = 0 \\
\quad where \quad x \in \{x_1, x_2, ..., x_n\}</code></pre>
<p>注意 <code class="katex-inline">F(x)</code> 只在 <code class="katex-inline">\{x_1, x_2, ..., x_n\}</code> 取值为 0，所以这些点是 <code class="katex-inline">F(x)</code> 的根</p>
<p>我们定义 <code class="katex-inline">Z_S(X) = (x - x_1)(x - x_2)...(x - x_n)</code>， 那么 <code class="katex-inline">Z_S(x)</code> 一定可以被 <code class="katex-inline">F(x)</code> 整除，即：<code class="katex-inline">Z_S(x) \mid F(x)</code></p>
<p>因此，我们一定可以求得 <code class="katex-inline">t_g(x)</code>， 使其满足：<code class="katex-inline">F(x) = t_g(x) \cdot {Z_S}(x)</code></p>
<p>注意：使用长除法的前提：知道 8 个符号的信息</p>
<p><strong>反思</strong> 零知识证明 和 电路</p>
<p>首先，一套电路系统有 已知输入 <code class="katex-inline">x</code>， 已知输出 <code class="katex-inline">y</code> 和 未知输入 <code class="katex-inline">w</code>；问题为：是否存在 <code class="katex-inline">w</code>， 满足 <code class="katex-inline">C(x, w) = y</code></p>
<p>我们再看多项式：</p>
<pre><code class="language-katex">{Q_L}(x) \cdot a(x) + {Q_R}(x) \cdot b(x) + {Q_O}(x) \cdot c(x) + {Q_M}(x) \cdot a(x) \cdot b(x) + {Q_C}(x) = 0 \\
where \quad x \in \{x_1, x_2, ..., x_n\}</code></pre>
<p>其中，</p>
<p><strong>关键</strong></p>
<ul>
<li>
<p>5 个 <code class="katex-inline">Q_x</code> 选择系数</p>
<ul>
<li>代表一套已经构建的电路中，每个门的元信息 (每个门是加法门，还是乘法门，还是常数门，或布尔门)</li>
<li>是已知信息</li>
</ul>
</li>
<li>
<p><code class="katex-inline">a</code> 和 <code class="katex-inline">b</code> 和 <code class="katex-inline">c</code></p>
<ul>
<li>代表每个门的 输入和输出</li>
<li>是否已知，取决于 P 是否已经求出 <code class="katex-inline">C(x, w) = y</code> 的解</li>
<li>如果 P 解出 <code class="katex-inline">w</code>， 它就结合 已知输入 <code class="katex-inline">x</code> 运算电路，得到每个门的输入和输出</li>
</ul>
</li>
</ul>
<h3>复制约束</h3>
<p>首先，把 <code class="katex-inline">3n</code> 个变量看成一个集合，则 复制约束 可以看成对这个集合的 partition 划分：把一个集合拆分成若干个子集的并，且子集之间不相交；即：<code class="katex-inline">F = F_1 \cup F_2 \cup ... \cup F_k</code></p>
<p>然后，对每个子集 <code class="katex-inline">F_i</code> 进行置换，得到 <code class="katex-inline">\{\sigma_1, \sigma_2, ..., \sigma_k\}</code>；然后把所有子集的置换连乘，得到整体置换 <code class="katex-inline">\sigma</code>；即：<code class="katex-inline">\sigma = \sigma_1 \cdot \sigma_2 \cdot ... \cdot \sigma_k</code></p>
<p>所以：</p>
<pre><code class="language-katex">\sigma \begin{bmatrix}
a_1 &amp; a_2 &amp; a_3 &amp; ... &amp; a_n \\
b_1 &amp; b_2 &amp; b_3 &amp; ... &amp; b_n \\
c_1 &amp; c_2 &amp; c_3 &amp; ... &amp; c_n \\
\end{bmatrix}
= \begin{bmatrix}
a_x &amp; a_x &amp; a_x &amp; ... &amp; a_x \\
b_x &amp; b_x &amp; b_x &amp; ... &amp; b_x \\
c_x &amp; c_x &amp; c_x &amp; ... &amp; c_x \\
\end{bmatrix}</code></pre>
<p>这个置换方程，也就覆盖了所有的 复制约束 copy constraint</p>
<p><strong>TODO 没懂</strong></p>
<pre><code class="language-katex">\prod_{i = 1}^{N} U_i = 1</code></pre>
<p>问题：怎样把 <code class="katex-inline">U_1 \cdot U_2 \cdot ... \cdot U_N = 1</code> 变成 <code class="katex-inline">U(x)</code> (注意不是 <code class="katex-inline">U(x_i)</code>) 的方程</p>
<p>解答：PLONK 用 递归 来处理</p>
<p>定义 <code class="katex-inline">Z_1</code>， <code class="katex-inline">Z_2</code>， ...，<code class="katex-inline">Z_{N+1}</code>， 递归如下</p>
<pre><code class="language-katex">\begin{cases}
Z_1 = 1 \\
Z_2 = U_1 \\
Z_3 = U_1 \cdot U_2 \\
... \\
Z_N = U_1 \cdot U_2 \cdot ... \cdot U_{N-1} \\
Z_{N+1} = 1 = Z_1 \\
\end{cases}</code></pre>
<p>统一表述为 <code class="katex-inline">Z_{i+1} = Z_i \cdot U_i</code></p>
<p>进一步，我们发现 <code class="katex-inline">Z(x_{i+1}) = Z(x_i) \cdot U(x_i)</code></p>
<p>这样，我们几乎得到了想要的 <code class="katex-inline">U(x)</code> 的方程，除了 问号 部分：<code class="katex-inline">Z(?) = Z(x) \cdot U(x)</code></p>
<p>解决：在 有限域 <code class="katex-inline">\mathbb{F}_{p}</code> 的 乘法群 <code class="katex-inline">\mathbb{F}^*_{p}</code> 中找到带 单位根 的 <code class="katex-inline">n</code> 阶子群 <code class="katex-inline">H</code>：<code class="katex-inline">H = \{\omega, \omega^2, ..., \omega^n = 1\}</code></p>
<p>TODO '单位根'</p>
<p><strong>TODO 没懂</strong></p>
<p>参考 <a href="#门约束">门约束</a>，我们对门的标记不是 <code class="katex-inline">\{1, 2, ..., n\}</code>， 而是 <code class="katex-inline">H</code> (<code class="katex-inline">\{\omega, \omega^2, ..., \omega^n = 1\}</code>)</p>
<p>这样我们得到 问号 部分：<code class="katex-inline">Z(\omega \cdot x) = Z(x) \cdot U(x)</code></p>
<p>通过这个结论，我们就把一个很复杂的乘积，转成了多项式</p>
<h3>总结</h3>
<p>我们把一个电路的问题，完完全全翻译成一些多项式组成的方程组</p>
<p>准确的说，是这两种方程的形式：</p>
<pre><code class="language-katex">\begin{cases}
\begin{aligned}
&amp; {Q_L}(x) \cdot a(x) + {Q_R}(x) \cdot b(x) + {Q_O}(x) \cdot c(x) + {Q_M}(x) \cdot a(x) \cdot b(x) + {Q_C}(x) = 0 &amp; where \quad x \in \{x_1, x_2, ..., x_n\} \\
&amp; Z(\omega \cdot x) - Z(x) \cdot U(x) = 0 &amp; where \quad x \in \{\omega, \omega^2, ..., \omega^n = 1\} \\
\end{aligned}
\end{cases}</code></pre>
<p>分别对应 门约束 和 复制约束</p>
<h2>6 - 递归证明</h2>
<p>命题：<code class="katex-inline">R(x, w) = 1</code></p>
<p>P：向 V 出示 <code class="katex-inline">\pi</code>， 证明自己解出 <code class="katex-inline">w</code></p>
<p>V：验证 <code class="katex-inline">V(x, \pi) = 1</code></p>
<p>递归：</p>
<p>V 向 V1 出示 <code class="katex-inline">\pi'</code>， 证明 自己已经拿到了 P 的一个证明，即：<code class="katex-inline">V(x, \pi) = 1</code></p>
<p>V1：验证 <code class="katex-inline">V_1(x, \pi') = 1</code></p>
<p>--</p>
<p>问题：为什么要递归，而不是由 V 直接将 <code class="katex-inline">\pi</code> 给 V1</p>
<p>原因：区块链 scalability</p>
<p>命题：存在交易 <code class="katex-inline">t_n</code>， 满足状态转移方程：</p>
<pre><code class="language-katex">\exist tn: U(\sigma_{n-1}, t_n) = \sigma_n</code></pre>
<p>P 向 V 出示 <code class="katex-inline">\pi_n</code></p>
<pre><code class="language-katex">\begin{cases}
\pi_1 \equiv \exist t_1: U(\sigma_0, t_1) = \sigma_1 \\
\pi_2 \equiv \exist t_2: U(\sigma_1, t_2) = \sigma_2 \\
\dots \\
\pi_n \equiv \exist tn: U(\sigma_{n-1}, t_n) = \sigma_n \\
\end{cases}</code></pre>
<p>V 进行验证</p>
<pre><code class="language-katex">\begin{cases}
V(\sigma_0, \sigma_1, \pi_1) = 1 \\
V(\sigma_1, \sigma_2, \pi_2) = 1 \\
V(\sigma_2, \sigma_3, \pi_3) = 1 \\
V(\sigma_3, \sigma_4, \pi_4) = 1 \\
\dots \\
V(\sigma_{n-1}, \sigma_n, \pi_n) = 1 \\
\end{cases}</code></pre>
<p>接下来，两两合并：</p>
<p>命题：存在 <code class="katex-inline">\sigma_{n-1}</code> 和 <code class="katex-inline">\pi_{n-1}</code> 和 <code class="katex-inline">\pi_n</code>， 使得 <code class="katex-inline">V(\sigma_{n-2}, \sigma_{n-1}, \pi_{n-1}) = 1</code> 和 <code class="katex-inline">V(\sigma_{n-1}, \sigma_n, \pi_n) = 1</code> 同时成立：</p>
<pre><code class="language-katex">\exist \sigma_{n-1}, \pi_{n-1}, \pi_{n}: V(\sigma_{n-2}, \sigma_{n-1}, \pi_{n-1}) = 1 \wedge V(\sigma_{n-1}, \sigma_n, \pi_n) = 1</code></pre>
<p>V 需要出示声明 <code class="katex-inline">\pi'_1</code>， 证明上面式子成立；</p>
<p>V‘ 进行验证 <code class="katex-inline">V(\sigma_0, \sigma_2, \pi'_1) = 1</code></p>
<p>--</p>
<p>20 层递归，可以证明 <code class="katex-inline">2^{20}</code> 笔交易</p>
<p>最后只需要一个 <code class="katex-inline">\pi</code>， 就能证明 整个 状态转换 链条 合法</p>
<h2>7 - 整体过程分析与总结</h2>
<p><strong>原始问题：零知识证明 <code class="katex-inline">R(x, w) = 1</code> </strong></p>
<p>其中，<code class="katex-inline">R</code> 是个数学算法，可以检查 <code class="katex-inline">w</code> 是否是 <code class="katex-inline">x</code> 的解；<code class="katex-inline">x</code> 是其个问题，<code class="katex-inline">w</code> 是问题的解</p>
<p>P 和 V 都知道 <code class="katex-inline">R</code>，<code class="katex-inline">x</code>， 这是公开信息；</p>
<p>只有 P 解出 <code class="katex-inline">w</code>， 这是私密信息，他向 V 证明</p>
<p><strong>把 零知识证明 转为 电路问题：<code class="katex-inline">C(x, w) = y</code></strong></p>
<p>其中，<code class="katex-inline">C</code> 表示公开的电一套路结构，<code class="katex-inline">x</code> 表示 公开的输入，<code class="katex-inline">y</code> 表示 公开的输出，P 和 V 都知道</p>
<p><code class="katex-inline">w</code> 表示 未知输入，P 解出后要向 V 证明</p>
<p><strong>把 电路问题 转为 约束系统</strong></p>
<p>原来的电路系统就被表示成了约束系统，存在两种约束</p>
<p>(1) 门约束</p>
<p><code class="katex-inline">i</code> 表示门的下标，每个门有一个门约束</p>
<pre><code class="language-katex">({Q_L}_i) a_i + ({Q_R}_i) b_i + ({Q_O}_i) c_i + ({Q_M}_i) a_i b_i + {Q_C}_i = 0</code></pre>
<p>(2) 复制约束</p>
<p>另一个门的输出，是另一个门的输入；因此要约束 输入 等于 输出</p>
<p>通过置换，把整套电路系统的所有 复制约束，一起表达为方程：</p>
<pre><code class="language-katex">\sigma \begin{bmatrix}
a_1 &amp; a_2 &amp; a_3 &amp; ... &amp; a_n \\
b_1 &amp; b_2 &amp; b_3 &amp; ... &amp; b_n \\
c_1 &amp; c_2 &amp; c_3 &amp; ... &amp; c_n \\
\end{bmatrix}
= \begin{bmatrix}
a_x &amp; a_x &amp; a_x &amp; ... &amp; a_x \\
b_x &amp; b_x &amp; b_x &amp; ... &amp; b_x \\
c_x &amp; c_x &amp; c_x &amp; ... &amp; c_x \\
\end{bmatrix}</code></pre>
<p><strong>把 门约束系统 转为 多项式</strong></p>
<p>对门约束系统，把带 <code class="katex-inline">i</code> 的参数，转为 <code class="katex-inline">i</code> 的函数 (纵向看待 5 个选择系数 和 <code class="katex-inline">a</code>，<code class="katex-inline">b</code>，<code class="katex-inline">c</code>)</p>
<p>得到多项式：</p>
<pre><code class="language-katex">F(x) = {Q_L}(x) \cdot a(x) + {Q_R}(x) \cdot b(x) + {Q_O}(x) \cdot c(x) + {Q_M}(x) \cdot a(x) \cdot b(x) + {Q_C}(x) = 0 \\
where \quad x \in \{x_1, x_2, ..., x_n\}</code></pre>
<p>再次强调：<code class="katex-inline">F(x)</code> 只在 <code class="katex-inline">\{x_1, x_2, ..., x_n\}</code> 取值为 0，所以这些点是 <code class="katex-inline">F(x)</code> 的根</p>
<p>我们定义 <code class="katex-inline">Z_S(X) = (x - x_1)(x - x_2)...(x - x_n)</code>， 那么 <code class="katex-inline">Z_S(x)</code> 一定可以被 <code class="katex-inline">F(x)</code> 整除，即：<code class="katex-inline">Z_S(x) \mid F(x)</code></p>
<p>因此，我们一定可以求得 <code class="katex-inline">t_g(x)</code>， 使其满足：<code class="katex-inline">F(x) = t_g(x) \cdot {Z_S}(x)</code></p>
<p><strong>把 复制约束系统 转为 多项式</strong></p>
<p><strong>TODO 没懂</strong></p>
<p>第一步，把 置换方程 变成 代数方程</p>
<p>第二步，把 代数方程 换成 多项式 (把 <code class="katex-inline">U(x_i)</code> 变成 <code class="katex-inline">U(x)</code>)</p>
<pre><code class="language-katex">Z(\omega \cdot x) - Z(x) \cdot U(x) = 0 \\
where \quad x \in \{\omega, \omega^2, ..., \omega^n = 1\}</code></pre>
<p>因为多项式 <code class="katex-inline">{Z(\omega \cdot x) - Z(x) \cdot U(x)}</code> 在 <code class="katex-inline">\{\omega, \omega^2, ..., \omega^n = 1\}</code> 这些点上取值为 0，所以这些点是它的根</p>
<p>我们定义 <code class="katex-inline">Z_H(X) = (x - \omega)(x - \omega^2)...(x - \omega^n)</code>， 那么 <code class="katex-inline">Z_H(x)</code> 一定可以被 <code class="katex-inline">{Z(\omega \cdot x) - Z(x) \cdot U(x)}</code> 整除，即：<code class="katex-inline">Z_H(x) \mid Z(\omega \cdot x) - Z(x) \cdot U(x)</code></p>
<p>因此，我们一定可以求得 <code class="katex-inline">t_c(x)</code>， 使其满足：<code class="katex-inline">Z(\omega \cdot x) - Z(x) \cdot U(x) = t_c(x) \cdot {Z_H}(x)</code></p>
<h3>结合 门约束 和 复制约束</h3>
<p>我们用 复制约束系统 中的 <code class="katex-inline">\{\omega, \omega^2, ..., \omega^n = 1\}</code>， 作为 门约束系统中 <code class="katex-inline">\{x_1, x_2, ..., x_n\}</code> 的取值，即：<code class="katex-inline">Z_S(x) \equiv Z_H(x)</code></p>
<p>那么，原始的 零知识证明 问题 可以转为：</p>
<pre><code class="language-katex">\exist a(x), b(x), c(x), Z(x), t_g(x), t_c(x) \\
such \ that \\
\begin{cases}
{Q_L}(x) \cdot a(x) + {Q_R}(x) \cdot b(x) + {Q_O}(x) \cdot c(x) + {Q_M}(x) \cdot a(x) \cdot b(x) + {Q_C}(x) = t_g(x) \cdot {Z_H}(x)\\
Z(\omega \cdot x) - U(x) \cdot Z(x) = t_c(x) \cdot {Z_H}(x)\\
\end{cases}</code></pre>
<p>P 和 V 都知道的公共信息：<code class="katex-inline">{Q_L}(x)</code>，<code class="katex-inline">{Q_R}(x)</code>，<code class="katex-inline">{Q_O}(x)</code>，<code class="katex-inline">{Q_M}(x)</code>，<code class="katex-inline">{Q_C}(x)</code>，<code class="katex-inline">Z_H(x)</code></p>
<p>P 知道的 但 V 不知道：<code class="katex-inline">a(x)</code>，<code class="katex-inline">b(x)</code>，<code class="katex-inline">c(x)</code>，<code class="katex-inline">Z(\omega \cdot x)</code>，<code class="katex-inline">t_g(x)</code>，<code class="katex-inline">t_c(x)</code></p>
<p>--</p>
<p>为了简洁，用 <code class="katex-inline">G</code> 代表公共信息 <code class="katex-inline">{Q_L}(x)</code>，<code class="katex-inline">{Q_R}(x)</code>，<code class="katex-inline">{Q_O}(x)</code>，<code class="katex-inline">{Q_M}(x)</code>，<code class="katex-inline">{Q_C}(x)</code>，<code class="katex-inline">Z_H(x)</code></p>
<p>我们可以进一步将上面两个多项式写为：</p>
<pre><code class="language-katex">G(x, a(x), b(x), c(x), Z(\omega \cdot x), t_g(x), t_c(x)) = 0</code></pre>
<p>P 需要向 V 证明，自己知道 <code class="katex-inline">a(x)</code>，<code class="katex-inline">b(x)</code>，<code class="katex-inline">c(x)</code>，<code class="katex-inline">Z(\omega \cdot x)</code>，<code class="katex-inline">t_g(x)</code>，<code class="katex-inline">t_c(x)</code> 满足上面的等式</p>
]]></content:encoded>
					
					<wfw:commentRss>https://godorz.info/2022/04/plonk-notes/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Compound 的潜在风险和改进</title>
		<link>https://godorz.info/2021/11/compound-review/</link>
					<comments>https://godorz.info/2021/11/compound-review/#respond</comments>
		
		<dc:creator><![CDATA[ripwu]]></dc:creator>
		<pubDate>Mon, 08 Nov 2021 01:57:53 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[blockchain]]></category>
		<category><![CDATA[defi]]></category>
		<guid isPermaLink="false">https://godorz.info/?p=1781</guid>

					<description><![CDATA[Compound 中一些潜在的风险，以及可能的改进]]></description>
										<content:encoded><![CDATA[<p>Compound 潜在风险和改进</p>
<p>之前在看 Compound 代码时，感觉存在一些疑问和改进</p>
<p>其中有个疑问昨天得到了回复，趁着这个机会简单整理下笔记</p>
<pre><code class="language-c"></code></pre>
<h2>退出市场的资产，仍可被清算</h2>
<h3>背景</h3>
<pre><code class="language-js">// compound-protocol/contracts/Comptroller.sol

function borrowAllowed(address cToken, address borrower, uint borrowAmount) external returns (uint) {
    if (!markets[cToken].accountMembership[borrower]) {
        // only cTokens may call borrowAllowed if borrower not in market
        require(msg.sender == cToken, &quot;sender must be cToken&quot;);

        // attempt to add borrower to the market
        Error err = addToMarketInternal(CToken(msg.sender), borrower);
        if (err != Error.NO_ERROR) {
            return uint(err);
        }

        // it should be impossible to break the important invariant
        assert(markets[cToken].accountMembership[borrower]);
    }
}

function addToMarketInternal(CToken cToken, address borrower) internal returns (Error) {
    Market storage marketToJoin = markets[address(cToken)];

    if (!marketToJoin.isListed) {
        // market is not listed, cannot join
        return Error.MARKET_NOT_LISTED;
    }

    if (marketToJoin.accountMembership[borrower] == true) {
        // already joined
        return Error.NO_ERROR;
    }

    // survived the gauntlet, add to list
    // NOTE: we store these somewhat redundantly as a significant optimization
    //  this avoids having to iterate through the list for the most common use cases
    //  that is, only when we need to perform liquidity checks
    //  and not whenever we want to check if an account is in a particular market
    marketToJoin.accountMembership[borrower] = true;
    accountAssets[borrower].push(cToken);

    emit MarketEntered(cToken, borrower);

    return Error.NO_ERROR;
}</code></pre>
<p>Compound 在借款时会通过 <code>borrowAllowed()</code> 检查用户是否已经进入 <code>cToken</code> 市场</p>
<p>如果未进入，会调用 <code>addToMarketInternal()</code> 将 <code>cToken</code> 添加到用户接触的资产列表 <code>accountAssets[borrower]</code> 中</p>
<p>我查了下 <code>accountAssets[borrower]</code>，似乎只在 存款，借款，和计算用户健康度时使用</p>
<p>其中前面两个操作 (存款，借款) 更多是类似声明的逻辑，没有什么疑点</p>
<pre><code class="language-js">// compound-protocol/contracts/Comptroller.sol

function getHypotheticalAccountLiquidityInternal(
    address account,
    CToken cTokenModify,
    uint redeemTokens,
    uint borrowAmount) internal view returns (Error, uint, uint) {
    // For each asset the account is in
    CToken[] memory assets = accountAssets[account];
    for (uint i = 0; i &lt; assets.length; i++) {
        CToken asset = assets[i];

        // Too Long Not Listed.
        // ...
    }
}</code></pre>
<p>用户健康度计算代码如上，在计算 <code>account</code> 健康度时，遍历的是 <code>accountAssets[account]</code></p>
<p>如果用户此前发起退出某个资产市场的交易，如 USDC，则这个资产不在 <code>accountAssets[account]</code> 中</p>
<p>这时，计算健康度会跳过用户的 USDC 资产</p>
<h3>清算</h3>
<p>上面梳理了背景逻辑，即：退出市场的资产，不会参与清算时用户健康度的计算</p>
<p>内在含义是：该资产可以作为存款收取利息，但由于退出了市场，不会做为抵押物</p>
<p>而在实际清算代码时，我没有找到有关清算交易指定的资产，是否不在用户的 <code>accountAssets</code> 列表中的判断</p>
<p>即已经退出市场，不会作为抵押物的资产，可以被清算..</p>
<pre><code class="language-js">// compound-protocol/contracts/CToken.sol

function liquidateBorrowFresh(address liquidator, address borrower, uint repayAmount, CTokenInterface cTokenCollateral) internal returns (uint, uint) {
    /* Fail if repayBorrow fails */
    (uint repayBorrowError, uint actualRepayAmount) = repayBorrowFresh(liquidator, borrower, repayAmount);
    if (repayBorrowError != uint(Error.NO_ERROR)) {
        return (fail(Error(repayBorrowError), FailureInfo.LIQUIDATE_REPAY_BORROW_FRESH_FAILED), 0);
    }

    /* We calculate the number of collateral tokens that will be seized */
    (uint amountSeizeError, uint seizeTokens) = comptroller.liquidateCalculateSeizeTokens(address(this), address(cTokenCollateral), actualRepayAmount);
    require(amountSeizeError == uint(Error.NO_ERROR), &quot;LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED&quot;);

    /* Revert if borrower collateral token balance &lt; seizeTokens */
    require(cTokenCollateral.balanceOf(borrower) &gt;= seizeTokens, &quot;LIQUIDATE_SEIZE_TOO_MUCH&quot;);

    // If this is also the collateral, run seizeInternal to avoid re-entrancy, otherwise make an external call
    uint seizeError;
    if (address(cTokenCollateral) == address(this)) {
        seizeError = seizeInternal(address(this), liquidator, borrower, seizeTokens);
    } else {
        seizeError = cTokenCollateral.seize(liquidator, borrower, seizeTokens);
    }

    return (uint(Error.NO_ERROR), actualRepayAmount);
}</code></pre>
<h3>测试</h3>
<p>我担心存在理解偏差，于是在 Ropsten 网络上进行了测试：</p>
<p>首先用账户 A 发送 <a href="https://ropsten.etherscan.io/tx/0x7b71d5cf083eca8ab436126953f87573fb9d047dced373394ba2d6ae4621e0a2">exitMarket</a> 交易，将存入的 cETH 退出市场</p>
<p>然后用账户 A 发送 <a href="https://ropsten.etherscan.io/tx/0xbb4691fdf1f81b9634375658862d7b7ec6ff7253e81f3896a6025bba11b1e54c">setUnderlyingPrice</a> 交易，操纵预言机，模拟市场价格波动，使得账户 A 资不抵债</p>
<p>最后用账户 B 发送 <a href="https://ropsten.etherscan.io/tx/0xa38099eb44664169e41e36d06ef0d72c241ddd0a4349e3f36f46506667c4c975">liquidateBorrow</a> 交易，清算账户 A 的债务，指定以 cETH 为抵押物</p>
<p>结论是：退出市场的 cETH 确实可以被清算</p>
<h3>问题</h3>
<p>问题来了：</p>
<p>问题一：已经退出市场的资产，是否应该被清算？</p>
<p>问题二：如果不应该被清算，那么进入市场和退出市场的逻辑，意义何在？</p>
<p>综合考虑，我个人觉得 Compound 原意应该是不允许清算已退出市场的资产；理由如下：</p>
<p>首先，用户在实际存款前必须单独发起进入市场的交易，考虑到 Compound 在以太坊主网运营，交易手续费不可忽视</p>
<p>如果可以被清算，那么进入和退出市场的逻辑没有什么实际用途，在代码中也未找到其他用途</p>
<p>其次，在退出市场前，Compound 提示如下</p>
<p><img decoding="async" src="/wp-content/uploads/2021/11/Compound_Disable_As_Collateral.jpeg" alt="Disable As Collateral" width="80%" height="80%" align="bottom" /></p>
<p>但是，从另外一个角度来说，退出市场的资产，确实应该支持被清算，否则有损于系统健康度</p>
<h3>反馈</h3>
<p>两个角度都有道理，我没想明白，于是向 Compound 发送了邮件，一周后收到了回复：问题已知，已退出市场的资产可以被清算；提示文本看起来是有误导</p>
<p>不过，我还是没明白：既然可以被清算，为什么要设计进入退出的功能，用户专门发起这两笔交易的手续费呢...</p>
<p><img decoding="async" src="/wp-content/uploads/2021/11/Compound_Liquidating_Exited_Asset2.png" alt="Liquidating Exited Asset" width="80%" height="80%" align="bottom" /></p>
<p>BTW，前两天 Aave V3 似乎也引入了 <a href="https://governance.aave.com/t/introducing-aave-v3/6035">资产隔离</a> 的概念..</p>
<h2>USDC 钉住 1 美元</h2>
<p>前面文章中有举例说明 Compound 价格预言机的流程，以 DAI 为例：首先向 USDC-WETH 交易对查询 WETH 价格，然后向 DAI-WETH 交易对查询 DAI 价格，最后将两者相乘，得到以 USDC 计价的 DAI 价格</p>
<p>换句话说，Compound 中大部分 token 的价格是以 USDC 计价的</p>
<p>这里隐藏了一个假设，USDC 价格是恒定不变的，可以作为计价单位</p>
<pre><code class="language-js">// https://github.com/smartcontractkit/open-oracle/blob/master/contracts/Uniswap/UniswapAnchoredView.sol

function priceInternal(TokenConfig memory config) internal view returns (uint) {
    if (config.priceSource == PriceSource.REPORTER) return prices[config.symbolHash].price;
    // config.fixedPrice holds a fixed-point number with scaling factor 10**6 for FIXED_USD
    if (config.priceSource == PriceSource.FIXED_USD) return config.fixedPrice;
    if (config.priceSource == PriceSource.FIXED_ETH) {
        uint usdPerEth = prices[ethHash].price;
        require(usdPerEth &gt; 0, &quot;ETH price not set, cannot convert to dollars&quot;);
        // config.fixedPrice holds a fixed-point number with scaling factor 10**18 for FIXED_ETH

        return mul(usdPerEth, config.fixedPrice) / ethBaseUnit;
    }
}</code></pre>
<p>实现上，Compound 对 USDC，USDT 等做了特殊处理，其 <code>priceSource</code> 配置为 <code>FIXED_USD</code>，钉在 1 美元</p>
<p>在 USDC 价格波动时，可能会导致一些问题，比如 <a href="https://www.comp.xyz/t/floating-stablecoin-prices/2005">这个提案</a> 描述的例子：</p>
<p>假设 USDC 因监管或其他原因不断下跌，比如市场价格为 0.5 美元，而 Compound 仍认为其价值 1 美元</p>
<p>由于存在价差，我们可以从外部市场低价借入 USDC，存入 Compound，将其高价抵押借出其他资产</p>
<p>造成的结果是，市场价格不断下跌的 USDC 涌入 Compound，而其他资产被不断借出</p>
<p>提案提出的问题，已经过去几个月了，没有得到官方回复..</p>
<h2>抵押率 与 清算阈值</h2>
<p>在比较 Compound 和 Aave 时，我发现 Compound 没有 <a href="https://godorz.info/2021/10/aave-v2/#i-19">Aave 清算阈值 (Liquidation Threshold)</a> 的概念</p>
<p>在用户体验上，这可能会带来一些问题：</p>
<p>如果用户在 Compound 按最大抵押率借款，只要市场价格稍有波动，其抵押资产就会面临清算风险</p>
<pre><code class="language-js">// compound-protocol/contracts/Comptroller.sol

function getHypotheticalAccountLiquidityInternal(
    address account,
    CToken cTokenModify,
    uint redeemTokens,
    uint borrowAmount) internal view returns (Error, uint, uint) {

    AccountLiquidityLocalVars memory vars; // Holds all our calculation results
    uint oErr;

    // For each asset the account is in
    CToken[] memory assets = accountAssets[account];
    for (uint i = 0; i &lt; assets.length; i++) {
        CToken asset = assets[i];

        vars.collateralFactor = Exp({mantissa: markets[address(asset)].collateralFactorMantissa});

        // Pre-compute a conversion factor from tokens -&gt; ether (normalized price value)
        vars.tokensToDenom = mul_(mul_(vars.collateralFactor, vars.exchangeRate), vars.oraclePrice);

        // sumCollateral += tokensToDenom * cTokenBalance
        vars.sumCollateral = mul_ScalarTruncateAddUInt(vars.tokensToDenom, vars.cTokenBalance, vars.sumCollateral);
    }
}</code></pre>
<p>其中，在计算 <code>sumCollateral</code> 时，使用的是抵押率 <code>collateralFactor</code></p>
<p>--</p>
<p>与之相对的，在 Aave 中，贷款时按抵押率计算，而清算时健康度按清算阈值计算；因为清算阈值比抵押率大，因此留出了安全垫</p>
<p>引用链接中的例子：用户抵押价值 2 ETH 的资产，借出 1.575 ETH 的债务，此时健康度为 1.0476</p>
<p>注意例子中的债务，是按资产的最大抵押率借出的；在这种情况下，可以忍受市场价格小范围的波动</p>
<p>比如，市场价格短期波动，导致债务上涨 3% 时，此时健康度仍在 1 以上，用户资产不会面临清算风险</p>
<h2>隐患</h2>
<h3>不在官方仓库中的代码</h3>
<p>比如价格预言机，还未被合并，见 <a href="https://godorz.info/2021/11/compound_comp_and_price_oracles/#i-8">Compound 代币和价格预言</a></p>
<p>又如，官方仓库中 <code>Comptroller</code>，似乎也是<a href="https://github.com/compound-finance/compound-protocol/blob/master/contracts/ComptrollerStorage.sol">较老的版本</a>；而主网实际使用的合约，是修复了 9 月底 COMP 安全事件的<a href="https://etherscan.io/address/0xbafe01ff935c7305907c33bf824352ee5979b526#code">版本</a></p>
<p>--</p>
<p>对于新入手 Compound 的开发者而言，要找到正确的代码，只能求助于 EtherScan 和搜索引擎，体验有点糟糕</p>
<p>更重要的是，会导致接下来的问题：</p>
<h3>不同步的主网与测试网络</h3>
<p>对于价格预言机，考虑到链下数据不好维护，为了便于测试，可以在测试网部署模拟合约作为 mock</p>
<p>除此之外，应该尽可能保证其他合约在主网和测试网一致，但在 Compound 中并非如此：</p>
<p>比如，最核心的 <code>Unitroller</code>，在 <a href="https://etherscan.io/address/0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B#readProxyContract">主网</a> 与 <a href="https://ropsten.etherscan.io/address/0xcfa7b0e37f5AC60f3ae25226F5e39ec59AD26152#readProxyContract">测试网络</a> 上部署的代码版本不同</p>
<p>又如 <code>CErc20Immutable</code> 是旧代码，会导致 cToken <a href="https://www.comp.xyz/t/legacy-market-migration-wbtc/1333">无法支持社区治理</a>。主网中这个合约已被废弃，但在测试中仍在使用，比如 Ropsten 中的 <a href="https://ropsten.etherscan.io/address/0x2973e69b20563bcc66dC63Bde153072c33eF37fe#code">cUSDC</a></p>
<p>--</p>
<p>主网与测试网络之间的不同步，除了削弱测试网络的意义，也增加了新开发者的理解成本</p>
<p>要解决这个问题，首先要解决前面的问题，确保官方仓库与主网部署的合约代码一致</p>
<p>这也就引出了更关键的问题：</p>
<h3>测试网络似乎没有发生作用</h3>
<p><a href="https://github.com/rebase-network/Dapp-Learning/blob/main/defi/Compound/contract/%5B%E4%BA%8B%E4%BB%B6%E5%88%86%E6%9E%90%5D%209%E6%9C%8829%E6%97%A5%20Compound%2062%E5%8F%B7%E6%8F%90%E6%A1%88%20%E6%89%80%E5%BC%95%E5%8F%91%E7%9A%84%E5%8F%AF%E6%80%95Bug.md">COMP 安全事件</a> 暴露的问题比较严重：考虑到除了公开的测试网络之外，社区中还有不少开发者搭建着私人测试网络，而理论上，这个问题是必现的；</p>
<p>我们似乎可以得出一个结论：Compound 的测试网络和 <a href="https://github.com/compound-finance/compound-protocol/tree/master/spec">测试代码</a>，没有起到作用</p>
<p>那么，Compound 协议安全如何保证呢？社区成员似乎也在担心，比如最近几天出现的提案 <a href="https://www.comp.xyz/t/auditing-compound-protocol/2543">Auditing Compound Protocol</a>，<a href="https://www.comp.xyz/t/continuous-formal-verification/2557">Continuous Formal Verification</a></p>
<p>--</p>
<p>另外，还有代码与文档/产品之间的不同步，原始的升级模式等；限于个人视野未知全貌，某些理解可能存在局限，因此不做展开</p>
<p>以上，一家之言，欢迎指正～</p>
]]></content:encoded>
					
					<wfw:commentRss>https://godorz.info/2021/11/compound-review/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Compound 代币和价格预言</title>
		<link>https://godorz.info/2021/11/compound_comp_and_price_oracles/</link>
					<comments>https://godorz.info/2021/11/compound_comp_and_price_oracles/#respond</comments>
		
		<dc:creator><![CDATA[ripwu]]></dc:creator>
		<pubDate>Wed, 03 Nov 2021 02:38:15 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[blockchain]]></category>
		<category><![CDATA[defi]]></category>
		<guid isPermaLink="false">http://godorz.info/?p=1770</guid>

					<description><![CDATA[Compound 中 COMP 代币挖矿 和 价格预言机的实现]]></description>
										<content:encoded><![CDATA[<p>Compound 白皮书和核心代码，大佬已经写了很详细的文档，见</p>
<p><a href="https://github.com/rebase-network/Dapp-Learning/blob/main/defi/Compound/whitepaper/Compound%E7%99%BD%E7%9A%AE%E4%B9%A6%E7%AE%80%E8%BF%B0.md">Compound白皮书简述</a><br />
<a href="https://github.com/rebase-network/Dapp-Learning/blob/main/defi/Compound/contract/Compound%E5%90%88%E7%BA%A6%E9%83%A8%E7%BD%B2.md">Compound合约部署</a><br />
<a href="https://github.com/rebase-network/Dapp-Learning/blob/main/defi/Compound/contract/Compound%E5%90%88%E7%BA%A6%E5%8D%87%E7%BA%A7%E6%A8%A1%E5%BC%8F.md">Compound合约升级模式</a></p>
<p>这里补充下周边： COMP 代币 和 价格预言</p>
<pre><code class="language-c"></code></pre>
<h2>COMP</h2>
<h3>投放计划</h3>
<p>为了激励用户，用户每次存款或者借款，Compound 都会奖励 COMP 代币，可以用于治理投票</p>
<p>COMP 每日总产出约为 2312 枚，各市场的分布见 <a href="https://compound.finance/governance/com">文档</a>，部分市场如下</p>
<table>
<thead>
<tr>
<th style="text-align: left;">Market</th>
<th style="text-align: right;">Per Day</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">DAI</td>
<td style="text-align: right;">880.38</td>
</tr>
<tr>
<td style="text-align: left;">Ether</td>
<td style="text-align: right;">141.25</td>
</tr>
<tr>
<td style="text-align: left;">USDC</td>
<td style="text-align: right;">880.38</td>
</tr>
<tr>
<td style="text-align: left;">USDT</td>
<td style="text-align: right;">126.80</td>
</tr>
</tbody>
</table>
<p>每个市场，借款和存款产出的 COMP，分别占 50%</p>
<p>以 USDC 市场为例，每日共产出 880.38 枚 COMP，其中通过借款的方式投放 440.19 枚 COMP，借款用户按其借款额度占总借款额度的比例分配；存款同理</p>
<h3>配置</h3>
<p>如上所述，根据各市场每日产出的 COMP 数量，按每 15 秒一个区块的假设，可以得到每个区块产出的 COMP 数量，记录在 <code>ComptrollerV6Storage</code> 中</p>
<pre><code class="language-js">contract ComptrollerV6Storage is ComptrollerV5Storage {
    // https://compound.finance/governance/comp

    /// @notice The rate at which comp is distributed to the corresponding borrow market (per block)
    mapping(address =&gt; uint) public compBorrowSpeeds;

    /// @notice The rate at which comp is distributed to the corresponding supply market (per block)
    mapping(address =&gt; uint) public compSupplySpeeds;
}</code></pre>
<p><code>compBorrowSpeeds</code> 和 <code>comSupplySpeeds</code> 为 <code>cToken</code> 到每区块产出 COMP 数量的映射</p>
<p>比如对 <code>cUSDC</code> 来说，它在两个映射表中的值都为 67000000000000000 (COMP 的精度为 <code class="katex-inline">{10}^{18}</code>)</p>
<pre><code class="language-katex">\frac{2 \times 67000000000000000 \times 86400}{15} \approx 880.38 \times {10}^{18}</code></pre>
<h3>存款挖矿</h3>
<p>用户每次操作，只要可能更新存款，如存款操作，会触发 <code>mintAllowed()</code>，它进一步</p>
<p>- 调用 <code>updateCompSupplyIndex()</code> 更新当前市场的 COMP 存款指数</p>
<p>- 调用 <code>distributeSupplierComp()</code> 分发当前用户此前未结算的存款产出的 COMP</p>
<pre><code class="language-js">function mintAllowed(address cToken, address minter, uint mintAmount) external returns (uint) {
    // Keep the flywheel moving
    updateCompSupplyIndex(cToken);
    distributeSupplierComp(cToken, minter);

    return uint(Error.NO_ERROR);
}</code></pre>
<p>--</p>
<p>当前市场的 COMP 存款指数更新逻辑如下</p>
<pre><code class="language-js">/**
* @notice Accrue COMP to the market by updating the supply index
* @param cToken The market whose supply index to update
* @dev Index is a cumulative sum of the COMP per cToken accrued.
*/
function updateCompSupplyIndex(address cToken) internal {
    CompMarketState storage supplyState = compSupplyState[cToken];
    uint supplySpeed = compSupplySpeeds[cToken];
    uint32 blockNumber = safe32(getBlockNumber(), &quot;block number exceeds 32 bits&quot;);
    uint deltaBlocks = sub_(uint(blockNumber), uint(supplyState.block));
    if (deltaBlocks &gt; 0 &amp;&amp; supplySpeed &gt; 0) {
        uint supplyTokens = CToken(cToken).totalSupply();
        uint compAccrued = mul_(deltaBlocks, supplySpeed);

        Double memory ratio = supplyTokens &gt; 0 ? fraction(compAccrued, supplyTokens) : Double({mantissa: 0});

        supplyState.index = safe224(add_(Double({mantissa: supplyState.index}), ratio).mantissa, &quot;new index exceeds 224 bits&quot;);
        supplyState.block = blockNumber;
    } else if (deltaBlocks &gt; 0) {
        supplyState.block = blockNumber;
    }
}</code></pre>
<p>首先判断距离上次更新指数，经过了几个区块 <code>deltaBlocks</code>，另外根据 <code>supplySpeed</code> 判断当前市场是否产出 COMP (0x, Aave 等配置为 0，表示不产出)</p>
<p>条件都满足后，计算 COMP 产出数量，除以 <code>cToken</code> 总供给，得到这几个区块间，平均每个 <code>cToken</code> 对应的 COMP 产出，即代码中的 <code>ratio</code></p>
<p>也就是说，<code>ratio</code> 可以理解为每持有一个 <code>cToken</code> ，可以得到多少 COMP</p>
<p>最后将 <code>ratio</code> 累加进 COMP 存款指数</p>
<p>--</p>
<p>当前用户此前未结算的 COMP 分发逻辑如下</p>
<pre><code class="language-js">/**
* @notice Calculate COMP accrued by a supplier and possibly transfer it to them
* @param cToken The market in which the supplier is interacting
* @param supplier The address of the supplier to distribute COMP to
*/
function distributeSupplierComp(address cToken, address supplier) internal {
    // TODO: Don&#039;t distribute supplier COMP if the user is not in the supplier market.
    // This check should be as gas efficient as possible as distributeSupplierComp is called in many places.
    // - We really don&#039;t want to call an external contract as that&#039;s quite expensive.

    CompMarketState storage supplyState = compSupplyState[cToken];
    uint supplyIndex = supplyState.index;
    uint supplierIndex = compSupplierIndex[cToken][supplier];

    // Update supplier&#039;s index to the current index since we are distributing accrued COMP
    compSupplierIndex[cToken][supplier] = supplyIndex;

    if (supplierIndex == 0 &amp;&amp; supplyIndex &gt;= compInitialIndex) {
        // Covers the case where users supplied tokens before the market&#039;s supply state index was set.
        // Rewards the user with COMP accrued from the start of when supplier rewards were first
        // set for the market.
        supplierIndex = compInitialIndex;
    }

    // Calculate change in the cumulative sum of the COMP per cToken accrued
    Double memory deltaIndex = Double({mantissa: sub_(supplyIndex, supplierIndex)});

    uint supplierTokens = CToken(cToken).balanceOf(supplier);

    // Calculate COMP accrued: cTokenAmount * accruedPerCToken
    uint supplierDelta = mul_(supplierTokens, deltaIndex);

    uint supplierAccrued = add_(compAccrued[supplier], supplierDelta);
    compAccrued[supplier] = supplierAccrued;

    emit DistributedSupplierComp(CToken(cToken), supplier, supplierDelta, supplyIndex);
}</code></pre>
<p>首先获取市场最新的 COMP 存款指数，以及用户此前结算时的指数，相减得到 <code>deltaIndex</code></p>
<p>然后乘以用户持有的 <code>cToken</code> 数量，得到用户这段时间应该获得的 COMP</p>
<p>--</p>
<p>需要说明的是，这里结算的是用户之前的存款，占当前总供给的百分比，不会算入用户接下来马上将改变的存款</p>
<p>换句话说，存款余额的修改，要在至少一个区块之后才会被用于结算 COMP，即用户操作与 COMP 结算是跨区块的</p>
<p>算是降低了被闪电贷攻击的风险</p>
<h3>借款挖矿</h3>
<p>与存款挖矿大同小异，稍微复杂一些，这里不再赘述</p>
<h3>通胀</h3>
<p>根据 messari，COMP 的 Inflation Rate 为 <a href="https://messari.io/asset/compound/metrics/supply">27.50%</a></p>
<p>我没找到其确切公式，不过我们可以自行计算，根据 2021-11-05 和 2022-11-04 的 <a href="https://messari.io/asset/compound/profile/supply-schedule">流动性投放计划</a> ，简单相除得到通胀系数为 27.34%；和 messari 数据相比，算是大差不差了</p>
<p><img decoding="async" src="/wp-content/uploads/2021/11/compound_COMP_2021-2022.png" alt="COMP 2021-2022" /></p>
<table>
<thead>
<tr>
<th style="text-align: center;">-</th>
<th style="text-align: right;">2021-11-05</th>
<th style="text-align: right;">2022-11-04</th>
<th style="text-align: right;">Inflation Rate</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;">User</td>
<td style="text-align: right;">1,473,555</td>
<td style="text-align: right;">2,527,335</td>
<td style="text-align: right;"></td>
</tr>
<tr>
<td style="text-align: center;">Founder and Team</td>
<td style="text-align: right;">866,200</td>
<td style="text-align: right;">1,421,300</td>
<td style="text-align: right;"></td>
</tr>
<tr>
<td style="text-align: center;">Shareholders</td>
<td style="text-align: right;">2,396,307</td>
<td style="text-align: right;">2,396,307</td>
<td style="text-align: right;"></td>
</tr>
<tr>
<td style="text-align: center;">Community</td>
<td style="text-align: right;">775,000</td>
<td style="text-align: right;">775,000</td>
<td style="text-align: right;"></td>
</tr>
<tr>
<td style="text-align: center;">Future team members</td>
<td style="text-align: right;">372,797</td>
<td style="text-align: right;">372,797</td>
<td style="text-align: right;"></td>
</tr>
<tr>
<td style="text-align: center;"><code class="katex-inline">\sum</code></td>
<td style="text-align: right;">5,883,859</td>
<td style="text-align: right;">7,492,379</td>
<td style="text-align: right;">+27.34%</td>
</tr>
</tbody>
</table>
<p>但是，这里有个统计陷阱：Founders &amp; team 分批 vest 且 Future team members 也未兑现，部分流动性没有进入市场，因此分母偏大了</p>
<p>也就是说，实际通胀率还要高出不少</p>
<p>不管怎样，通胀率接近甚至超过 30% 的资产，价格稳定在 $100 ~ $300；我看不懂，但我大受震撼～</p>
<h3>安全</h3>
<p>9月29日 Compound 发生一起安全事件，详见 <a href="https://github.com/rebase-network/Dapp-Learning/blob/main/defi/Compound/contract/%5B%E4%BA%8B%E4%BB%B6%E5%88%86%E6%9E%90%5D%209%E6%9C%8829%E6%97%A5%20Compound%2062%E5%8F%B7%E6%8F%90%E6%A1%88%20%E6%89%80%E5%BC%95%E5%8F%91%E7%9A%84%E5%8F%AF%E6%80%95Bug.md">[事件分析] 9月29日 Compound 62号提案 所引发的可怕Bug</a></p>
<p>其中，Robert Leshner 提到的 <code>Reservori</code> 合约 (<a href="https://etherscan.io/address/0x2775b1c75658Be0F640272CCb8c72ac986009e38">地址</a>)，就是上面投放计划中 <code>User</code> (借贷挖矿) 的 COMP 来源</p>
<h2>价格预言机</h2>
<p>Compound 同时使用 Uniswap v2 和 Chainlink v2 作为价格预言机</p>
<p>Chainlink 价格以 Uniswap 价格为锚，前者作为实际价格，后者作为基准价格</p>
<p>Chainlink 价格需要在 Uniswap 价格的某段浮动范围内，才能作为有效价格被更新到预言机</p>
<h3>代码</h3>
<p><a href="https://github.com/compound-finance/open-oracle">compound-finance/open-oracle</a> 中只有 Uniswap 相关代码，我找遍 branches 和 tags 都没找到 Chainlink 部分</p>
<p>最后在 Compound 社区找到这个关于添加 Chainlink 预言机提案的精彩讨论 <a href="https://www.comp.xyz/t/oracle-infrastructure-chainlink-proposal/1272">Oracle Infrastructure: Chainlink Proposal</a></p>
<p>成果是 Chainlink 团队在 Compound 原有 Open Price Feed 的代码基础上，集成了 Chainlink 聚合器的报价，并进一步做了部署和测试；Compound 社区通过治理，应用了新的预言机</p>
<p>然而，Chainlink 提交的 PR：<a href="https://github.com/compound-finance/open-oracle/pull/150">Oracle Improvement (Chainlink Price Feeds) #150</a>，改动较多，还卡在审核阶段，未被合并..</p>
<p>因此，最新代码不在官方仓库中</p>
<p>审计报告见 <a href="https://drive.google.com/file/d/1TsOXhBLenStjdd2mxF1Sfmmh_Na9X527/view">Trail of Bits: Chainlink Open-Oracle Summary Report</a></p>
<p>以下分析基于 Chainklink fork 的仓库 <a href="https://github.com/smartcontractkit/open-oracle/blob/master/contracts/Uniswap/UniswapAnchoredView.sol">smartcontractkit/open-source</a></p>
<h3>实现</h3>
<pre><code class="language-js">/**
 * @notice This is called by the reporter whenever a new price is posted on-chain
 * @dev called by AccessControlledOffchainAggregator
 * @param currentAnswer the price
 * @return valid bool
 */
function validate(uint256/* previousRoundId */,
        int256 /* previousAnswer */,
        uint256 /* currentRoundId */,
        int256 currentAnswer) external override returns (bool valid) {

    // NOTE: We don&#039;t do any access control on msg.sender here. The access control is done in getTokenConfigByReporter,
    // which will REVERT if an unauthorized address is passed.
    TokenConfig memory config = getTokenConfigByReporter(msg.sender);
    uint256 reportedPrice = convertReportedPrice(config, currentAnswer);
    uint256 anchorPrice = calculateAnchorPriceFromEthPrice(config);

    PriceData memory priceData = prices[config.symbolHash];
    if (priceData.failoverActive) {
        require(anchorPrice &lt; 2**248, &quot;Anchor price too large&quot;);
        prices[config.symbolHash].price = uint248(anchorPrice);
        emit PriceUpdated(config.symbolHash, anchorPrice);
    } else if (isWithinAnchor(reportedPrice, anchorPrice)) {
        require(reportedPrice &lt; 2**248, &quot;Reported price too large&quot;);
        prices[config.symbolHash].price = uint248(reportedPrice);
        emit PriceUpdated(config.symbolHash, reportedPrice);
        valid = true;
    } else {
        emit PriceGuarded(config.symbolHash, reportedPrice, anchorPrice);
    }
}</code></pre>
<p>核心代码如上所示</p>
<p><code>validate()</code> 由 Chainlink 调用，参数 <code>currentAnswer</code> 表示 Chainlink 链下统计的价格，单位由 Chainlink 控制</p>
<p>以 DAI 为例，假设 <code>currentAnswer</code> 为 100055330</p>
<p>为了方便处理，<code>convertReportedPrice()</code> 将其转为内部单位，得到 1000553</p>
<p><code>calculateAnchorPriceFromEthPrice()</code> 通过向交易对询价得到链上 Uniswap 交易所的价格，比如为 1001190</p>
<p>接下来判断 <code>failoverActive</code>，这是由社区投票决定的一项配置，表示当前市场 (DAI) 是否忽略 Chainlink 价格，以 Uniswap 价格为准</p>
<p>否则，通过 <code>isWithAnchor()</code> 确认 Chainlink 价格在 Uniswap 价格浮动范围内 ([85%, 115%])</p>
<p>--</p>
<pre><code class="language-js">/**
 * @notice Calculate the anchor price by fetching price data from the TWAP
 * @param config TokenConfig
 * @return anchorPrice uint
 */
function calculateAnchorPriceFromEthPrice(TokenConfig memory config) internal returns (uint anchorPrice) {
    uint ethPrice = fetchEthAnchorPrice();
    require(config.priceSource == PriceSource.REPORTER, &quot;only reporter prices get posted&quot;);
    if (config.symbolHash == ethHash) {
        anchorPrice = ethPrice;
    } else {
        anchorPrice = fetchAnchorPrice(config.symbolHash, config, ethPrice);
    }
}

/**
 * @dev Fetches the current eth/usd price from uniswap, with 6 decimals of precision.
 *  Conversion factor is 1e18 for eth/usdc market, since we decode uniswap price statically with 18 decimals.
 */
function fetchEthAnchorPrice() internal returns (uint) {
    return fetchAnchorPrice(ethHash, getTokenConfigBySymbolHash(ethHash), ethBaseUnit);
}

/**
 * @dev Fetches the current token/usd price from uniswap, with 6 decimals of precision.
 * @param conversionFactor 1e18 if seeking the ETH price, and a 6 decimal ETH-USDC price in the case of other assets
 */
function fetchAnchorPrice(bytes32 symbolHash, TokenConfig memory config, uint conversionFactor) internal virtual returns (uint) {
    (uint nowCumulativePrice, uint oldCumulativePrice, uint oldTimestamp) = pokeWindowValues(config);

    // This should be impossible, but better safe than sorry
    require(block.timestamp &gt; oldTimestamp, &quot;now must come after before&quot;);
    uint timeElapsed = block.timestamp - oldTimestamp;

    // Calculate uniswap time-weighted average price
    // Underflow is a property of the accumulators: https://uniswap.org/audit.html#orgc9b3190
    FixedPoint.uq112x112 memory priceAverage = FixedPoint.uq112x112(uint224((nowCumulativePrice - oldCumulativePrice) / timeElapsed));
    uint rawUniswapPriceMantissa = priceAverage.decode112with18();
    uint unscaledPriceMantissa = mul(rawUniswapPriceMantissa, conversionFactor);
    uint anchorPrice;

    // Adjust rawUniswapPrice according to the units of the non-ETH asset
    // In the case of ETH, we would have to scale by 1e6 / USDC_UNITS, but since baseUnit2 is 1e6 (USDC), it cancels

    // In the case of non-ETH tokens
    // a. pokeWindowValues already handled uniswap reversed cases, so priceAverage will always be Token/ETH TWAP price.
    // b. conversionFactor = ETH price * 1e6
    // unscaledPriceMantissa = priceAverage(token/ETH TWAP price) * expScale * conversionFactor
    // so -&gt;
    // anchorPrice = priceAverage * tokenBaseUnit / ethBaseUnit * ETH_price * 1e6
    //             = priceAverage * conversionFactor * tokenBaseUnit / ethBaseUnit
    //             = unscaledPriceMantissa / expScale * tokenBaseUnit / ethBaseUnit
    anchorPrice = mul(unscaledPriceMantissa, config.baseUnit) / ethBaseUnit / expScale;

    emit AnchorPriceUpdated(symbolHash, anchorPrice, oldTimestamp, block.timestamp);

    return anchorPrice;
}</code></pre>
<p>接下来，简单看下 Uniswap 询价逻辑</p>
<p>首先通过 <code>fetchEthAnchorPrice()</code> 从交易对 <a href="https://etherscan.io/address/0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc">USDC-WETH</a> 获得按 USDC 计价 (单位 <code class="katex-inline">10^{6}</code>) 的 WETH 的价格，比如为 4351156768</p>
<p>然后通过 <code>fetchAnchorPrice()</code> 从交易对 <a href="https://etherscan.io/address/0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11">DAI-WETH</a> 获得按 WETH 计价 (单位 <code class="katex-inline">10^{18}</code>) 的 DAI 的价格，比如为 230097482692738</p>
<p>上面两个价格相乘，得到 1001190219118269813150784</p>
<p>最后，转换单位，得到按 USDC 计价的 DAI 价格，即上面的 1001190</p>
<h3>代理</h3>
<p><code>UniswapAnchoredView</code> 自身可能升级，因此会存在新旧合约实例；升级过程中，我们必须保证两个合约的价格预言同步，且经过一段时间验证后，经由社区投票，用新合约代替旧合约，以此完成升级</p>
<p>然而，依据 Chainlink 的设计，聚合器只能向一个合约地址发送喂价</p>
<p>为了解决这个问题，在 Chainlink 聚合器与 Compound 之间，引入了一层代理合约 <code>ValidatorProxy</code>，它将聚合器的报价同时转发给新旧 <code>UniswapAnchoredView</code> 合约</p>
<p>由于采用的是 报价 (push) 而非 询价 (pull) 的方式，更新价格的成本由 Chainlink 承担，因此 Compound 用户无须额外支付代理层带来的 gas</p>
<p>审计报告见 <a href="https://drive.google.com/file/d/1u12kitAyQKwe3mJVFh5ePzabTmwhjA2Y/view">Sigma Prime: Chainlink ValidatorProxy Security Assessment Report</a></p>
<p>代码在另一个仓库中: <a href="https://github.com/smartcontractkit/chainlink/blob/develop/contracts/src/v0.8/ValidatorProxy.sol">smartcontractkit/chainlink</a></p>
<pre><code class="language-js">function validate(
    uint256 previousRoundId,
    int256 previousAnswer,
    uint256 currentRoundId,
    int256 currentAnswer
) external override returns (bool)
{
    // Send the validate call to the current validator
    ValidatorConfiguration memory currentValidator = s_currentValidator;
    address currentValidatorAddress = address(currentValidator.target);
    require(currentValidatorAddress != address(0), &quot;No validator set&quot;);
    currentValidatorAddress.call(
        abi.encodeWithSelector(
        AggregatorValidatorInterface.validate.selector,
        previousRoundId,
        previousAnswer,
        currentRoundId,
        currentAnswer
        )
    );

    // If there is a new proposed validator, send the validate call to that validator also
    if (currentValidator.hasNewProposal) {
        address(s_proposedValidator).call(
        abi.encodeWithSelector(
            AggregatorValidatorInterface.validate.selector,
            previousRoundId,
            previousAnswer,
            currentRoundId,
            currentAnswer
        )
        );
    }
    return true;
}</code></pre>
<p>逻辑非常直白了..</p>
]]></content:encoded>
					
					<wfw:commentRss>https://godorz.info/2021/11/compound_comp_and_price_oracles/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>深入探索 CALL 指令参数0</title>
		<link>https://godorz.info/2021/10/dive-into-call-param0/</link>
					<comments>https://godorz.info/2021/10/dive-into-call-param0/#respond</comments>
		
		<dc:creator><![CDATA[ripwu]]></dc:creator>
		<pubDate>Mon, 25 Oct 2021 01:57:15 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[blockchain]]></category>
		<guid isPermaLink="false">http://godorz.info/?p=1705</guid>

					<description><![CDATA[`CALL` 指令首个参数的用途，以及外部调用时 gas 的计算]]></description>
										<content:encoded><![CDATA[<p>此前和几位朋友交流过智能合约外部调用的问题，有点久了；最近开始有些时间，简单整理记录下</p>
<p>外部调用有好几种指令，下面以最常见的 <code>CALL</code> 为例</p>
<h2>问题</h2>
<p>讨论最多的是 <code>CALL</code> 指令的参数0 <code>gas</code> 具体的作用，比如：</p>
<p>问题一：参数0 是否无用，为什么 <code>opCall()</code> 中直接 pop 掉了？</p>
<pre><code class="language-go">func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
    stack := scope.Stack
    // Pop gas. The actual gas in interpreter.evm.callGasTemp.
    // We can use this as a temporary value
    temp := stack.pop()
    gas := interpreter.evm.callGasTemp
    // Pop other call parameters.
    addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()

    // Too Long Not Listed
    // ...
}</code></pre>
<p>问题二：此前 <a href="https://godorz.info/2021/09/paradigm_ctf_2021_part_1/#BabySandbox">Paradigm CTF 2021: BabySandbox</a> 题解，能否稍做解释？</p>
<p>问题三：题解测试有效，但为什么修改原题，<code>CALL</code> 时 0x4000 改为 0x30000 的话，题解无效？</p>
<pre><code class="language-js">pragma solidity 0.7.0;

contract BabySandbox {
    function run(address code) external payable {
        assembly {
            // if we&#039;re calling ourselves, perform the privileged delegatecall
            if eq(caller(), address()) {
                switch delegatecall(gas(), code, 0x00, 0x00, 0x00, 0x00)
                    case 0 {
                        returndatacopy(0x00, 0x00, returndatasize())
                        revert(0x00, returndatasize())
                    }
                    case 1 {
                        returndatacopy(0x00, 0x00, returndatasize())
                        return(0x00, returndatasize())
                    }
            }

            // ensure enough gas
            if lt(gas(), 0xf000) {
                revert(0x00, 0x00)
            }

            // load calldata
            calldatacopy(0x00, 0x00, calldatasize())

            // run using staticcall
            // if this fails, then the code is malicious because it tried to change state
            if iszero(staticcall(0x4000, address(), 0, calldatasize(), 0, 0)) {
                revert(0x00, 0x00)
            }

            // if we got here, the code wasn&#039;t malicious
            // run without staticcall since it&#039;s safe
            switch call(0x4000, address(), 0, 0, calldatasize(), 0, 0)
                case 0 {
                    returndatacopy(0x00, 0x00, returndatasize())
                    // revert(0x00, returndatasize())
                }
                case 1 {
                    returndatacopy(0x00, 0x00, returndatasize())
                    return(0x00, returndatasize())
                }
        }
    }
}</code></pre>
<h2>定义</h2>
<p>黄皮书关于 gas 机制的介绍，确实比较散乱..</p>
<p>要解释上面的问题，首先需要理解 <code>CALL</code> 的定义：</p>
<pre><code class="language-katex">\mathbf{i} \equiv \boldsymbol{\mu}_{\mathbf{m}}[ \boldsymbol{\mu}_{\mathbf{s}}[3] \dots (\boldsymbol{\mu}_{\mathbf{s}}[3] + \boldsymbol{\mu}_{\mathbf{s}}[4] - 1) ]</code></pre>
<pre><code class="language-katex">\begin{aligned}
(\boldsymbol{\sigma}&#039;, g&#039;, A^+, \mathbf{o}) \equiv \begin{cases}{\Theta}(\boldsymbol{\sigma}, I_{\mathrm{a}}, I_{\mathrm{o}}, t, t, C_{\text{\tiny CALLGAS}}(\boldsymbol{\mu}), I_{\mathrm{p}}, \boldsymbol{\mu}_{\mathbf{s}}[2], \boldsymbol{\mu}_{\mathbf{s}}[2], \mathbf{i}, I_{\mathrm{e}} + 1, I_{\mathrm{w}}) &amp; \text{if} \ \boldsymbol{\mu}_{\mathbf{s}}[2] \leqslant \boldsymbol{\sigma}[I_{\mathrm{a}}]_{\mathrm{b}} \;\wedge I_{\mathrm{e}} &lt; 1024 \\ (\boldsymbol{\sigma}, g, \varnothing, ()) &amp; \text{otherwise} \end{cases}
\end{aligned}</code></pre>
<pre><code class="language-katex">n \equiv \min(\{ \boldsymbol{\mu}_{\mathbf{s}}[6], \lVert \mathbf{o} \rVert\})</code></pre>
<pre><code class="language-katex">\boldsymbol{\mu}&#039;_{\mathbf{m}}[ \boldsymbol{\mu}_{\mathbf{s}}[5] \dots (\boldsymbol{\mu}_{\mathbf{s}}[5] + n - 1) ] = \mathbf{o}[0 \dots (n - 1)]</code></pre>
<pre><code class="language-katex">\boldsymbol{\mu}&#039;_{\mathbf{o}} = \mathbf{o}</code></pre>
<pre><code class="language-katex">\boldsymbol{\mu}&#039;_{\mathrm{g}} \equiv \boldsymbol{\mu}_{\mathrm{g}} + g&#039;</code></pre>
<pre><code class="language-katex">\boldsymbol{\mu}&#039;_{\mathbf{s}}[0] \equiv x</code></pre>
<pre><code class="language-katex">A&#039; \equiv A \Cup A^+</code></pre>
<pre><code class="language-katex">t \equiv \boldsymbol{\mu}_{\mathbf{s}}[1] \bmod 2^{160}</code></pre>
<p>where <code class="katex-inline">x=0</code> if the code execution for this operation failed due to an <code class="katex-inline">{exceptional\ halting}</code> (or for a <code class="katex-inline">\text{\small REVERT}</code>) <code class="katex-inline">\boldsymbol{\sigma}' = \varnothing</code> or if <code class="katex-inline">\boldsymbol{\mu}_{\mathbf{s}}[2] > \boldsymbol{\sigma}[I_{\mathrm{a}}]_{\mathrm{b}}</code> (not enough funds) or <code class="katex-inline">I_{\mathrm{e}} = 1024</code> (call depth limit reached); <code class="katex-inline">x=1</code> otherwise.</p>
<pre><code class="language-katex">\boldsymbol{\mu}&#039;_{\mathrm{i}} \equiv M(M(\boldsymbol{\mu}_{\mathrm{i}}, \boldsymbol{\mu}_{\mathbf{s}}[3], \boldsymbol{\mu}_{\mathbf{s}}[4]), \boldsymbol{\mu}_{\mathbf{s}}[5], \boldsymbol{\mu}_{\mathbf{s}}[6])</code></pre>
<p>Thus the operand order is: gas, to, value, in offset, in size, out offset, out size.</p>
<pre><code class="language-katex">C_{\text{\tiny CALL}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) \equiv C_{\text{\tiny GASCAP}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) + C_{\text{\tiny EXTRA}}(\boldsymbol{\sigma}, \boldsymbol{\mu})</code></pre>
<pre><code class="language-katex">C_{\text{\tiny CALLGAS}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) \equiv \begin{cases} C_{\text{\tiny GASCAP}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) + G_{\mathrm{callstipend}} &amp; \text{if} \quad \boldsymbol{\mu}_{\mathbf{s}}[2] \neq 0 \\ C_{\text{\tiny GASCAP}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) &amp; \text{otherwise} \end{cases}</code></pre>
<pre><code class="language-katex">C_{\text{\tiny GASCAP}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) \equiv \begin{cases} \min\{ L(\boldsymbol{\mu}_{\mathrm{g}} - C_{\text{\tiny EXTRA}}(\boldsymbol{\sigma}, \boldsymbol{\mu})), \boldsymbol{\mu}_{\mathbf{s}}[0] \} &amp; \text{if} \quad \boldsymbol{\mu}_{\mathrm{g}} \ge C_{\text{\tiny EXTRA}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) \\ \boldsymbol{\mu}_{\mathbf{s}}[0] &amp; \text{otherwise}\end{cases}</code></pre>
<pre><code class="language-katex">C_{\text{\tiny EXTRA}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) \equiv G_{\mathrm{call}} + C_{\text{\tiny XFER}}(\boldsymbol{\mu}) + C_{\text{\tiny NEW}}(\boldsymbol{\sigma}, \boldsymbol{\mu})</code></pre>
<pre><code class="language-katex">C_{\text{\tiny XFER}}(\boldsymbol{\mu}) \equiv \begin{cases}G_{\mathrm{callvalue}} &amp; \text{if} \quad \boldsymbol{\mu}_{\mathbf{s}}[2] \neq 0 \\0 &amp; \text{otherwise} \end{cases}</code></pre>
<pre><code class="language-katex">C_{\text{\tiny NEW}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) \equiv \begin{cases} G_{\mathrm{newaccount}} &amp; \text{if} \quad \mathtt{DEAD}(\boldsymbol{\sigma}, \boldsymbol{\mu}_{\mathbf{s}}[1] \bmod 2^{160}) \wedge \boldsymbol{\mu}_{\mathbf{s}}[2] \neq 0 \\ 0 &amp; \text{otherwise}\end{cases}</code></pre>
<h2>资料</h2>
<p>除了 <code>CALL</code> 自身定义外，可能还需要参考附录：</p>
<p><em>Appendix G. Fee Schedule</em></p>
<p><em>Appendix H. Virtual Machine Specification H.1. Gas Cost</em></p>
<h2>填坑</h2>
<p><a href="https://godorz.info/2021/09/paradigm_ctf_2021_part_1/#BabySandbox">Paradigm CTF 2021: BabySandbox</a> 题解挖了个坑，引出上面的问题二和问题三</p>
<p>这里尝试用另外一个坑的方式作为例子，算是把两个坑填一填～</p>
<p>在 <a href="https://godorz.info/2021/08/ethernaut/">Ethernaut 第13题 Gatekeeper One</a> 题解中，有提到对某些 OPCODE 的 GAS 存在疑惑</p>
<p>比如题解中的测试交易，gas 消耗状况如下</p>
<table>
<thead>
<tr>
<th style="text-align: left;">Step</th>
<th style="text-align: left;">PC</th>
<th style="text-align: left;">Operation</th>
<th style="text-align: left;">Gas</th>
<th style="text-align: left;">GasCost</th>
<th style="text-align: left;">Depth</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">[131]</td>
<td style="text-align: left;">377</td>
<td style="text-align: left;">EXTCODESIZE</td>
<td style="text-align: left;">2976410</td>
<td style="text-align: left;">2600</td>
<td style="text-align: left;">1</td>
</tr>
<tr>
<td style="text-align: left;">[132]</td>
<td style="text-align: left;">378</td>
<td style="text-align: left;">ISZERO</td>
<td style="text-align: left;">2973810</td>
<td style="text-align: left;">3</td>
<td style="text-align: left;">1</td>
</tr>
<tr>
<td style="text-align: left;">[133]</td>
<td style="text-align: left;">379</td>
<td style="text-align: left;">DUP1</td>
<td style="text-align: left;">2973807</td>
<td style="text-align: left;">3</td>
<td style="text-align: left;">1</td>
</tr>
<tr>
<td style="text-align: left;">[134]</td>
<td style="text-align: left;">380</td>
<td style="text-align: left;">ISZERO</td>
<td style="text-align: left;">2973804</td>
<td style="text-align: left;">3</td>
<td style="text-align: left;">1</td>
</tr>
<tr>
<td style="text-align: left;">[135]</td>
<td style="text-align: left;">381</td>
<td style="text-align: left;">PUSH2</td>
<td style="text-align: left;">2973801</td>
<td style="text-align: left;">3</td>
<td style="text-align: left;">1</td>
</tr>
<tr>
<td style="text-align: left;">[136]</td>
<td style="text-align: left;">384</td>
<td style="text-align: left;">JUMPI</td>
<td style="text-align: left;">2973798</td>
<td style="text-align: left;">10</td>
<td style="text-align: left;">1</td>
</tr>
<tr>
<td style="text-align: left;">[137]</td>
<td style="text-align: left;">389</td>
<td style="text-align: left;">JUMPDEST</td>
<td style="text-align: left;">2973788</td>
<td style="text-align: left;">1</td>
<td style="text-align: left;">1</td>
</tr>
<tr>
<td style="text-align: left;">[138]</td>
<td style="text-align: left;">390</td>
<td style="text-align: left;">POP</td>
<td style="text-align: left;">2973787</td>
<td style="text-align: left;">2</td>
<td style="text-align: left;">1</td>
</tr>
<tr>
<td style="text-align: left;">[139]</td>
<td style="text-align: left;">391</td>
<td style="text-align: left;">DUP8</td>
<td style="text-align: left;">2973785</td>
<td style="text-align: left;">3</td>
<td style="text-align: left;">1</td>
</tr>
<tr>
<td style="text-align: left;">[140]</td>
<td style="text-align: left;">392</td>
<td style="text-align: left;">CALL</td>
<td style="text-align: left;">2973782</td>
<td style="text-align: left;">2891523</td>
<td style="text-align: left;">1</td>
</tr>
<tr>
<td style="text-align: left;">[141]</td>
<td style="text-align: left;">0</td>
<td style="text-align: left;">PUSH1</td>
<td style="text-align: left;">2891423</td>
<td style="text-align: left;">3</td>
<td style="text-align: left;">2</td>
</tr>
</tbody>
</table>
<p>注意 [140]，在准备调用 <code>CALL</code> 前，堆栈和内存如下</p>
<p><img decoding="async" src="https://godorz.info/wp-content/uploads/2021/10/ehternaut_gatekeeperone_call_gas_remix_debug.png" alt="Remix Debug" /></p>
<p>结合 <code>CALL</code> 定义的相关公式和上面截图，可知此时</p>
<p><code class="katex-inline">\boldsymbol{\mu}_{\mathbf{s}}[0] = 0x2c1e9f = 2891423</code></p>
<p><code class="katex-inline">\boldsymbol{\mu}_{\mathbf{s}}[2] = 0</code></p>
<p><code class="katex-inline">\boldsymbol{\mu}_{\mathrm{g}} = 2973782</code></p>
<p>问题如下：</p>
<p><strong>为什么 [141] 的 Gas 为 2891423，而 [140] 的 GasCost 为 2891523，这两个数字是怎么来的？</strong></p>
<h2>解答</h2>
<p>上面例子中 <code class="katex-inline">(\boldsymbol{\mu}_{\mathbf{s}}[3], \boldsymbol{\mu}_{\mathbf{s}}[4]) \gt (\boldsymbol{\mu}_{\mathbf{s}}[5], \boldsymbol{\mu}_{\mathbf{s}}[6])</code></p>
<p>因此没有扩展内存，即内存相关的 gas 为 0</p>
<p>--</p>
<p>推导1 <code class="katex-inline">C_{\text{\tiny EXTRA}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) = 100</code></p>
<p>已知公式</p>
<pre><code class="language-katex">C_{\text{\tiny EXTRA}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) \equiv G_{\mathrm{call}} + C_{\text{\tiny XFER}}(\boldsymbol{\mu}) + C_{\text{\tiny NEW}}(\boldsymbol{\sigma}, \boldsymbol{\mu})</code></pre>
<pre><code class="language-katex">C_{\text{\tiny XFER}}(\boldsymbol{\mu}) \equiv \begin{cases}
G_{\mathrm{callvalue}} &amp; \text{if} \quad \boldsymbol{\mu}_{\mathbf{s}}[2] \neq 0 \\
0 &amp; \text{otherwise}
\end{cases}</code></pre>
<pre><code class="language-katex">C_{\text{\tiny NEW}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) \equiv \begin{cases}
G_{\mathrm{newaccount}} &amp; \text{if} \quad \mathtt{DEAD}(\boldsymbol{\sigma}, \boldsymbol{\mu}_{\mathbf{s}}[1] \bmod 2^{160}) \wedge \boldsymbol{\mu}_{\mathbf{s}}[2] \neq 0 \\
0 &amp; \text{otherwise}
\end{cases}</code></pre>
<p>又有 <code class="katex-inline">\boldsymbol{\mu}_{\mathbf{s}}[2]</code> 为 0</p>
<p>因此 <code class="katex-inline">C_{\text{\tiny XFER}}(\boldsymbol{\mu}) = 0</code> 且 <code class="katex-inline">C_{\text{\tiny NEW}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) = 0</code></p>
<p>因此 <code class="katex-inline">C_{\text{\tiny EXTRA}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) = G_{\mathrm{call}} + 0 + 0</code></p>
<p>再查看编译得到的 OPCODE</p>
<pre><code class="language-js">GatekeeperOne(target).enter.gas(sendGas)(_gateKey);</code></pre>
<p>上面代码会先通过 <code>EXTCODESIZE</code> 检查 <code>target</code> 是否存在源码，然后 <code>CALL</code> 时再对 <code>target</code> 发起消息调用。</p>
<p>根据 <a href="https://eips.ethereum.org/EIPS/eip-2929">EIP-2929: Gas cost increases for state access opcodes</a></p>
<p>前面 [131] 的 <code>EXTCODESIZE</code> 首次对该地址操作，消耗 2600 gas；因此接下来 [141] <code>CALL</code> 时，只需要消耗 100 gas，即 <code class="katex-inline">G_{\mathrm{call}} = 100</code></p>
<p>因此 <code class="katex-inline">C_{\text{\tiny EXTRA}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) = G_{\mathrm{call}} + 0 + 0 = 100</code></p>
<pre><code class="language-js">// github.com/ethereum/go-ethereum@v1.10.6/params/protocol_params.go

const (
    ColdAccountAccessCostEIP2929 = uint64(2600) // COLD_ACCOUNT_ACCESS_COST
    WarmStorageReadCostEIP2929   = uint64(100)  // WARM_STORAGE_READ_COST
)

// github.com/ethereum/go-ethereum@v1.10.6/core/vm/eips.go

func enable2929(jt *JumpTable) {
    jt[CALL].constantGas = params.WarmStorageReadCostEIP2929
    jt[CALL].dynamicGas = gasCallEIP2929
}

// github.com/ethereum/go-ethereum@v1.10.6/core/vm/operations_acl.go

var (
    gasCallEIP2929         = makeCallVariantGasCallEIP2929(gasCall)
)

func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc {
    return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
        addr := common.Address(stack.Back(1).Bytes20())
        // Check slot presence in the access list
        warmAccess := evm.StateDB.AddressInAccessList(addr)
        // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so
        // the cost to charge for cold access, if any, is Cold - Warm
        coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
        if !warmAccess {
            evm.StateDB.AddAddressToAccessList(addr)
            // Charge the remaining difference here already, to correctly calculate available
            // gas for call
            if !contract.UseGas(coldCost) {
                return 0, ErrOutOfGas
            }
        }
        // Now call the old calculator, which takes into account
        // - create new account
        // - transfer value
        // - memory expansion
        // - 63/64ths rule
        gas, err := oldCalculator(evm, contract, stack, mem, memorySize)
        if warmAccess || err != nil {
            return gas, err
        }
        // In case of a cold access, we temporarily add the cold charge back, and also
        // add it to the returned gas. By adding it to the return, it will be charged
        // outside of this function, as part of the dynamic gas, and that will make it
        // also become correctly reported to tracers.
        contract.Gas += coldCost
        return gas + coldCost, nil
    }
}</code></pre>
<p>--</p>
<p>推导2 <code class="katex-inline">C_{\text{\tiny GASCAP}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) = 2891423</code></p>
<p>已知公式</p>
<pre><code class="language-katex">C_{\text{\tiny GASCAP}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) \equiv \begin{cases}
\min\{ L(\boldsymbol{\mu}_{\mathrm{g}} - C_{\text{\tiny EXTRA}}(\boldsymbol{\sigma}, \boldsymbol{\mu})), \boldsymbol{\mu}_{\mathbf{s}}[0] \} &amp; \text{if} \quad \boldsymbol{\mu}_{\mathrm{g}} \ge C_{\text{\tiny EXTRA}}(\boldsymbol{\sigma}, \boldsymbol{\mu})\\
\boldsymbol{\mu}_{\mathbf{s}}[0] &amp; \text{otherwise}
\end{cases}</code></pre>
<p>其中</p>
<p>(318)</p>
<pre><code class="language-katex">L(n) \equiv n - \lfloor n / 64 \rfloor</code></pre>
<p><a href="https://medium.com/iovlabs-innovation-stories/the-dark-side-of-ethereum-1-64th-call-gas-reduction-ba661778568c">The Dark Side of Ethereum 1/64th CALL Gas Reduction</a></p>
<pre><code class="language-go">// github.com/ethereum/go-ethereum@v1.10.6/core/vm/gas.go

// callGas returns the actual gas cost of the call.
//
// The cost of gas was changed during the homestead price change HF.
// As part of EIP 150 (TangerineWhistle), the returned gas is gas - base * 63 / 64.
func callGas(isEip150 bool, availableGas, base uint64, callCost *uint256.Int) (uint64, error) {
    if isEip150 {
        availableGas = availableGas - base
        gas := availableGas - availableGas/64
        // If the bit length exceeds 64 bit we know that the newly calculated &quot;gas&quot; for EIP150
        // is smaller than the requested amount. Therefore we return the new gas instead
        // of returning an error.
        if !callCost.IsUint64() || gas &lt; callCost.Uint64() {
            return gas, nil
        }
    }
    if !callCost.IsUint64() {
        return 0, ErrGasUintOverflow
    }

    return callCost.Uint64(), nil
}</code></pre>
<p>参考截图，这里 <code class="katex-inline">\boldsymbol{\mu}_{\mathbf{s}}[0]</code> 为 2891423，<code class="katex-inline">\boldsymbol{\mu}_{\mathrm{g}}</code> 为 2973782，且如上所述 <code class="katex-inline">C_{\text{\tiny EXTRA}}(\boldsymbol{\sigma}, \boldsymbol{\mu})</code> 为 100</p>
<p>因此 <code class="katex-inline">C_{\text{\tiny GASCAP}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) = \min\{ (2973782 - 100) - \lfloor (2973782 - 100) / 64 \rfloor, 2891423 \} = 2891423</code></p>
<p>又根据公式</p>
<pre><code class="language-katex">C_{\text{\tiny CALLGAS}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) \equiv  \begin{cases}
C_{\text{\tiny GASCAP}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) + G_{\mathrm{callstipend}} &amp; \text{if} \quad \boldsymbol{\mu}_{\mathbf{s}}[2] \neq 0 \\
C_{\text{\tiny GASCAP}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) &amp; \text{otherwise}
\end{cases}</code></pre>
<p>因此 <code class="katex-inline">C_{\text{\tiny CALLGAS}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) = C_{\text{\tiny GASCAP}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) = 2891423</code></p>
<p>再根据公式</p>
<pre><code class="language-katex">\begin{aligned}
(\boldsymbol{\sigma}&#039;, g&#039;, A^+, \mathbf{o}) \equiv \begin{cases}{\Theta}(\boldsymbol{\sigma}, I_{\mathrm{a}}, I_{\mathrm{o}}, t, t, C_{\text{\tiny CALLGAS}}(\boldsymbol{\mu}), I_{\mathrm{p}}, \boldsymbol{\mu}_{\mathbf{s}}[2], \boldsymbol{\mu}_{\mathbf{s}}[2], \mathbf{i}, I_{\mathrm{e}} + 1, I_{\mathrm{w}}) &amp; \text{if} \ \boldsymbol{\mu}_{\mathbf{s}}[2] \leqslant \boldsymbol{\sigma}[I_{\mathrm{a}}]_{\mathrm{b}} \;\wedge I_{\mathrm{e}} &lt; 1024 \\ (\boldsymbol{\sigma}, g, \varnothing, ()) &amp; \text{otherwise} \end{cases}
\end{aligned}</code></pre>
<p>其中，<code class="katex-inline">{\Theta}</code> 第6个参数为表示目标合约的 gas</p>
<p>因此，[141] 的 Gas 为 <code>2891423</code></p>
<p>--</p>
<p>推导3 <code class="katex-inline">C_{\text{\tiny CALL}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) = 2891523</code></p>
<p>最后根据公式</p>
<pre><code class="language-katex">C_{\text{\tiny CALL}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) \equiv C_{\text{\tiny GASCAP}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) + C_{\text{\tiny EXTRA}}(\boldsymbol{\sigma}, \boldsymbol{\mu})</code></pre>
<p>因此，[140] 的 GasCost 为 <code class="katex-inline">C_{\text{\tiny CALL}}(\boldsymbol{\sigma}, \boldsymbol{\mu}) = 2891423 + 100 = 2891523</code></p>
<h2>小结</h2>
<p>理解上面的例子，应该就可以理解问题一和问题二了</p>
<p>至于问题三，为什么修改原题，加大 <code>CALL</code> 首个参数后题解无效？可以看看 <code class="katex-inline">C_{\text{\tiny GASCAP}}</code> 中的 <code>min</code></p>
<p>最后，此前的题解利用 <a href="https://eips.ethereum.org/EIPS/eip-2929">EIP-2929: Gas cost increases for state access opcodes</a> 的方式比较非主流，正经解答请参考官方题解~</p>
<p>最后的最后，可以思考下，假设当时题目首个参数确实为比较大的值，那么能否仍然利用 EIP-2929 解题呢？// 一时挖坑一时爽</p>
]]></content:encoded>
					
					<wfw:commentRss>https://godorz.info/2021/10/dive-into-call-param0/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>以太坊黄皮书学习笔记</title>
		<link>https://godorz.info/2021/10/ethereum-yellow-paper/</link>
					<comments>https://godorz.info/2021/10/ethereum-yellow-paper/#respond</comments>
		
		<dc:creator><![CDATA[ripwu]]></dc:creator>
		<pubDate>Fri, 22 Oct 2021 13:45:55 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[blockchain]]></category>
		<guid isPermaLink="false">http://godorz.info/?p=1638</guid>

					<description><![CDATA[此前学习以太坊黄皮书的笔记，结合 go-ethereum 源码，并且杂合了一些测试和脚本以加深理解]]></description>
										<content:encoded><![CDATA[
<p>这是此前学习以太坊黄皮书的笔记，因为结合了 go-ethereum 源码，并且笔记中杂合了一些测试和脚本以加深理解，结果笔记比较混乱..</p>
<p>最近觉得还是稍微整理发出来，抛砖引玉～</p>
<p>版本基于 VERSION 80085f7 – 2021-07-11</p>
<p>黄皮书混用了 <code class="katex-inline">=</code> 和 <code class="katex-inline">\equiv</code>，时而表示赋值，时而表示等价指代，注意区分</p>
<h2>2. The Blockchain Paradigm 区块链范式</h2>
<p>以太坊可以看作是一个交易驱动的状态机</p>
<p>公式 (1)</p>
<pre><code class="language-katex">\boldsymbol{\sigma}_{t+1} \equiv \Upsilon(\boldsymbol{\sigma}_{t}, T)</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">\Upsilon</code> 某次状态转移的函数</li>
<li><code class="katex-inline">{\sigma}</code> 状态</li>
</ul>
<p>挖矿</p>
<ul>
<li>挖矿
<ul>
<li>通过付出一定的工作量，与其他潜在区块竞争 一系列交易(一个区块) 的记账权</li>
</ul>
</li>
<li>系统激励
<ul>
<li>以状态转换函数的形式，给指定账户增加 ETH</li>
</ul>
</li>
</ul>
<p>公式 (2) (3) (4)</p>
<pre><code class="language-katex">\begin{aligned}
\boldsymbol{\sigma}_{t+1} &amp; \quad\equiv\quad {\Pi}(\boldsymbol{\sigma}_{t}, B) \\
B &amp; \quad\equiv\quad (..., (T_0, T_1, ...), ...) \\
\Pi(\boldsymbol{\sigma}, B) &amp; \quad\equiv\quad {\Omega}(B, {\Upsilon}(\Upsilon(\boldsymbol{\sigma}, T_0), T_1) ...)
\end{aligned}</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">\Omega</code> 区块奖励状态转换函数，参考公式 (169) (170) (171) (172)</li>
<li><code class="katex-inline">B</code> 表示一个区块，包含一系列交易和一些其他组成部分</li>
<li><code class="katex-inline">\Pi</code> 区块状态转换函数，参考公式 (177)</li>
</ul>
<h3>2.2 Which History? 如何选择历史?</h3>
<p>agreed-upon scheme</p>
<ul>
<li>blocktree 区块树
<ul>
<li>去中心化</li>
<li>每个参与者都有机会在前一个区块后，自己创建一个新的区块</li>
<li>形成一棵树(blocktree)</li>
</ul>
</li>
<li>共识
<ul>
<li>如何从根节点到叶节点形成一条区块链</li>
</ul>
</li>
<li>分叉
<ul>
<li>无法达成共识</li>
<li>各节点认可的 根节点到叶节点的路径(最佳区块链) 不同</li>
</ul>
</li>
</ul>
<p>(5)</p>
<pre><code class="language-katex">\beta = 1</code></pre>
<p><code class="katex-inline">\beta</code> chain ID，参考 <a href="https://eips.ethereum.org/EIPS/eip-155">EIP-155: Simple replay attack protection</a></p>
<h2>3. Conventions 约定</h2>
<h3>符号</h3>
<ul>
<li>
<p><code class="katex-inline">\boldsymbol{\sigma}</code>  世界状态(world-state)</p>
</li>
<li>
<p><code class="katex-inline">\boldsymbol{\mu}</code> 虚拟机状态(machine-state)</p>
</li>
<li>
<p><code class="katex-inline">\Upsilon</code> 状态转换函数</p>
</li>
<li>
<p><code class="katex-inline">C</code> 费用，例如<code class="katex-inline">C_\text{SSTORE}</code> 是执行 <code>SSTORE</code> 操作的费用</p>
</li>
<li>
<p><code class="katex-inline">\texttt{KEC}</code> Keccak-256</p>
</li>
<li>
<p><code class="katex-inline">T</code> 一笔以太坊交易</p>
</li>
<li>
<p><code class="katex-inline">n</code> nonce 用于防止重放攻击</p>
</li>
<li>
<p><code class="katex-inline">\mathbf{o}</code> 消息调用的输出</p>
</li>
<li>
<p><code class="katex-inline">\mathbb{B}_{32}</code> 长度为32的字节序列</p>
</li>
<li>
<p><code class="katex-inline">\mathbb{N}_{256}</code> 所有比 2^256 小的正整数</p>
</li>
<li>
<p><code class="katex-inline">\boldsymbol{\mu}_{\mathbf{s}}[0]</code> 虚拟机栈(stack)的栈顶元素</p>
</li>
<li>
<p><code class="katex-inline">\boldsymbol{\mu}_{\mathbf{m}}[0..31]</code> 虚拟机内存(memory)中的前32个条目</p>
</li>
<li>
<p><code class="katex-inline">\delta</code>某个 opcode 的出栈个数</p>
</li>
</ul>
<h3>状态</h3>
<p>熟悉下面三种表示方式，对于后文理解状态转换非常关键</p>
<ul>
<li><code class="katex-inline">\Box</code> 原始值/状态</li>
<li><code class="katex-inline">\Box'</code> 最终值/状态</li>
<li><code class="katex-inline">\Box^*</code>, <code class="katex-inline">\Box^{**}</code>, ... 中间值/状态</li>
</ul>
<h3>函数</h3>
<p>公式 (6) <code class="katex-inline">\ell</code> 求序列的末尾元素</p>
<pre><code class="language-katex">\ell(\mathbf{x}) \equiv \mathbf{x}[\lVert \mathbf{x} \rVert - 1]</code></pre>
<h2>4. Blocks, State and Transactions 区块，状态与交易</h2>
<h3>4.1 World Stage 世界状态</h3>
<p>参考</p>
<ul>
<li><a href="https://blog.csdn.net/wxudong1991/article/details/109311822">以太坊的数据存储结构</a></li>
<li><a href="https://blog.csdn.net/itleaks/article/details/79992072">以太坊MPT原理，你最值得看的一篇</a></li>
<li><a href="https://www.jianshu.com/p/d171aaa9770e">以太坊之PMT</a></li>
</ul>
<p>世界状态</p>
<ul>
<li>维护 账户地址 及其 账户状态 的映射
<ul>
<li><code class="katex-inline">a</code>
<ul>
<li>账户地址</li>
<li>大小为160位(20字节)</li>
</ul>
</li>
<li><code class="katex-inline">\boldsymbol{\sigma}[a]</code>
<ul>
<li>账户状态</li>
<li>含义
<ul>
<li>编码方式 RLP
<ul>
<li>Recursive Length Prefix</li>
<li>递归长度前缀编码</li>
</ul>
</li>
<li>用于序列化数据后传递到网络或存储</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>注意
<ul>
<li>世界状态不直接存储在区块链上</li>
</ul>
</li>
</ul>
<p><code class="katex-inline">\boldsymbol{\sigma}[a]</code> 账户状态</p>
<ul>
<li><code class="katex-inline">\boldsymbol{\sigma}[a]_{\mathrm{n}}</code>
<ul>
<li>nonce</li>
<li>含义
<ul>
<li>如果账户是钱包地址(非合约账户)，表示由此账户发出的交易数量</li>
<li>如果账户是智能合约，表示由此账户创建的合约数量</li>
</ul>
</li>
</ul>
</li>
<li><code class="katex-inline">\boldsymbol{\sigma}[a]_{\mathrm{b}}</code>
<ul>
<li>balance</li>
<li>余额</li>
</ul>
</li>
<li><code class="katex-inline">\boldsymbol{\sigma}[a]_{\mathrm{s}}</code>
<ul>
<li>storageRoot</li>
<li>含义
<ul>
<li>如果账户是智能合约
<ul>
<li>每个账户有自己的一棵 MRT 树，存储合约的内部状态(变量)
<ul>
<li>MPT (Merkle Patricia Tree)
<ul>
<li>Merkle Tree + Patricia Tree</li>
</ul>
</li>
</ul>
</li>
<li>storageRoot 就是树的根节点</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><code class="katex-inline">\boldsymbol{\sigma}[a]_{\mathrm{c}}</code>
<ul>
<li>codeHash</li>
<li>含义
<ul>
<li>账户持有的代码 bytecode 的 hash
<ul>
<li>创建后不可被修改</li>
<li>没有代码表示智能合约</li>
</ul>
</li>
</ul>
</li>
<li>推导
<ul>
<li>用 <code class="katex-inline">b</code> 来表示 代码</li>
<li>则 <code class="katex-inline">\texttt{KEC}(\mathbf{b}) = \boldsymbol{\sigma}[a]_{\mathrm{c}}</code></li>
</ul>
</li>
</ul>
</li>
</ul>
<p>公式 (7)</p>
<pre><code class="language-katex">\texttt{TRIE}\big(L_{\mathrm{I}}^*(\boldsymbol{\sigma}[a]_{\mathbf{s}})\big) \quad\equiv\quad \boldsymbol{\sigma}[a]_{\mathrm{s}}</code></pre>
<p>其中</p>
<p><code class="katex-inline">{\sigma}[a]_{\mathbf{s}}</code> 表示账户的所有内部状态组成的 TRIE 树的根节点</p>
<p>对这棵树的所有节点做坍塌转换，即可得到根节点的 hash 值</p>
<p>其中 公式 (8) (9)</p>
<pre><code class="language-katex">L_{\mathrm{I}}\big( (k, v) \big) \quad\equiv\quad \big(\texttt{KEC}(k), \texttt{RLP}(v)\big)</code></pre>
<pre><code class="language-katex">k \in \mathbb{B}_{32} \quad \wedge \quad v \in \mathbb{N}</code></pre>
<p>(10) 世界状态坍塌函数 <code class="katex-inline">L_{\mathrm{S}}</code></p>
<pre><code class="language-katex">L_{\mathrm{S}}(\boldsymbol{\sigma}) \quad\equiv\quad \{ p(a): \boldsymbol{\sigma}[a] \neq \varnothing \}</code></pre>
<p>其中 公式 (11)</p>
<pre><code class="language-katex">p(a) \quad\equiv\quad \big(\texttt{KEC}(a), \texttt{RLP}\big( (\boldsymbol{\sigma}[a]_{\mathrm{n}}, \boldsymbol{\sigma}[a]_{\mathrm{b}}, \boldsymbol{\sigma}[a]_{\mathrm{s}}, \boldsymbol{\sigma}[a]_{\mathrm{c}}) \big) \big)</code></pre>
<p>--</p>
<p>函数 <code class="katex-inline">L_{\mathrm{S}}</code> 和 Trie 函数一起用来提供一个世界状态的简短标识(hash)</p>
<p>我们假定:</p>
<p>公式 (12)</p>
<pre><code class="language-katex">\forall a: \boldsymbol{\sigma}[a] = \varnothing \; \vee \; (a \in \mathbb{B}_{20} \; \wedge \; v(\boldsymbol{\sigma}[a]))</code></pre>
<p>公式 (13) <code class="katex-inline">v</code> 表示账户有效性的验证函数</p>
<pre><code class="language-katex">v(x) \quad\equiv\quad x_{\mathrm{n}} \in \mathbb{N}_{256} \wedge x_{\mathrm{b}} \in \mathbb{N}_{256} \wedge x_{\mathrm{s}} \in \mathbb{B}_{32} \wedge x_{\mathrm{c}} \in \mathbb{B}_{32}</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">x_{\mathrm{n}}</code> nonce</li>
<li><code class="katex-inline">x_{\mathrm{b}}</code> balance</li>
<li><code class="katex-inline">x_{\mathrm{s}}</code> storageRoot</li>
<li><code class="katex-inline">x_{\mathrm{c}}</code> codeHash</li>
</ul>
<p>公式 (14)</p>
<pre><code class="language-katex">\mathtt{EMPTY}(\boldsymbol{\sigma}, a) \equiv \boldsymbol{\sigma}[a]_{\mathrm{c}} = \texttt{KEC}\big(()\big) \wedge \boldsymbol{\sigma}[a]_{\mathrm{n}} = 0 \wedge \boldsymbol{\sigma}[a]_{\mathrm{b}} = 0</code></pre>
<p>EMPTY 账户</p>
<ul>
<li>没有 code</li>
<li>且 nonce 为0</li>
<li>且 balance 为0</li>
</ul>
<p>公式 (15)</p>
<pre><code class="language-katex">\mathtt{DEAD}(\boldsymbol{\sigma}, a) \quad\equiv\quad \boldsymbol{\sigma}[a] = \varnothing \vee \mathtt{EMPTY}(\boldsymbol{\sigma}, a)</code></pre>
<p>DEAD 账户</p>
<ul>
<li>账户对应的状态不存在</li>
<li>或者它是 EMPTY 账户</li>
</ul>
<h3>4.2 The Transaction 交易</h3>
<p>两种交易类型</p>
<ul>
<li>消息调用 message call</li>
<li>合约创建 contract creation</li>
</ul>
<p>共同字段</p>
<ul>
<li><code class="katex-inline">T_{\mathrm{n}}</code>
<ul>
<li>nonce</li>
<li>由交易发送者发送的交易数量</li>
</ul>
</li>
<li><code class="katex-inline">T_{\mathrm{p}}</code>
<ul>
<li>gasPrice</li>
<li>gas 单位价格</li>
</ul>
</li>
<li><code class="katex-inline">T_{\mathrm{g}}</code>
<ul>
<li>gasLimit</li>
<li>执行这个交易的最大 gas</li>
</ul>
</li>
<li><code class="katex-inline">T_{\mathrm{t}}</code>
<ul>
<li>to</li>
<li>交易接收地址</li>
</ul>
</li>
<li><code class="katex-inline">T_{\mathrm{v}}</code>
<ul>
<li>value</li>
<li>含义
<ul>
<li>如果是消息调用交易
<ul>
<li>转移到交易接收者的 wei 的数量</li>
</ul>
</li>
<li>如果是合约创建交易
<ul>
<li>对新建合约的捐款</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><code class="katex-inline">T_{\mathrm{w}}</code> , <code class="katex-inline">T_{\mathrm{r}}</code> , <code class="katex-inline">T_{\mathrm{s}}</code>
<ul>
<li>v, r, s</li>
<li>含义
<ul>
<li>ECDSA 签名的3个组成部分</li>
<li>可以从中推导交易发起者 from address</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>特有字段</p>
<ul>
<li><code class="katex-inline">T_{\mathrm{i}}</code>
<ul>
<li>init</li>
<li>交易类型
<ul>
<li>合约创建</li>
</ul>
</li>
<li>含义
<ul>
<li>是一段 EVM-code，它将返回 body，这是这个账户每次接收到消息调用时回执行的代码</li>
<li>init 代码仅在合约创建时被执行一次，然后就被丢弃</li>
</ul>
</li>
</ul>
</li>
<li><code class="katex-inline">T_{\mathrm{d}}</code>
<ul>
<li>data</li>
<li>交易类型
<ul>
<li>消息调用</li>
</ul>
</li>
<li>含义
<ul>
<li>用于指定消息调用的输入数据</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>公式 (16)</p>
<pre><code class="language-katex">L_{\mathrm{T}}(T) \quad\equiv\quad \begin{cases}
(T_{\mathrm{n}}, T_{\mathrm{p}}, T_{\mathrm{g}}, T_{\mathrm{t}}, T_{\mathrm{v}}, T_{\mathbf{i}}, T_{\mathrm{w}}, T_{\mathrm{r}}, T_{\mathrm{s}}) &amp; \text{if} \; T_{\mathrm{t}} = \varnothing\\
(T_{\mathrm{n}}, T_{\mathrm{p}}, T_{\mathrm{g}}, T_{\mathrm{t}}, T_{\mathrm{v}}, T_{\mathbf{d}}, T_{\mathrm{w}}, T_{\mathrm{r}}, T_{\mathrm{s}}) &amp; \text{otherwise}
\end{cases}</code></pre>
<p>在这里，我们假设除了任意长度的字节数组 <code class="katex-inline">T_{\mathrm{i}}</code> 和 <code class="katex-inline">T_{\mathrm{d}}</code> 以外，所有变量都是作为整数来进行 RLP 编码</p>
<p>公式 (17)</p>
<pre><code class="language-katex">\begin{aligned}
&amp; T_{\mathrm{n}} \in \mathbb{N}_{256} &amp; \quad\wedge\quad &amp; T_{\mathrm{v}} \in \mathbb{N}_{256} &amp; \quad\wedge\quad &amp; T_{\mathrm{p}} \in \mathbb{N}_{256} \quad \wedge \\
&amp; T_{\mathrm{g}} \in \mathbb{N}_{256} &amp; \quad\wedge\quad &amp; T_{\mathrm{w}} \in \mathbb{N}_{256} &amp; \quad\wedge\quad &amp; T_{\mathrm{r}} \in \mathbb{N}_{256} \quad \wedge \\
&amp; T_{\mathrm{s}} \in \mathbb{N}_{256} &amp; \quad\wedge\quad &amp; T_{\mathbf{d}} \in \mathbb{B} &amp; \quad\wedge\quad &amp;  T_{\mathbf{i}} \in \mathbb{B}
\end{aligned}</code></pre>
<p>其中</p>
<p>公式 (18)</p>
<pre><code class="language-katex">\mathbb{N}_{\mathrm{n}} = \{ P: P \in \mathbb{N} \wedge P &lt; 2^n \}</code></pre>
<p>公式 (19)</p>
<pre><code class="language-katex">T_{\mathbf{t}} \in \begin{cases} \mathbb{B}_{20} &amp; \text{if} \quad T_{\mathrm{t}} \neq \varnothing \\
\mathbb{B}_{0} &amp; \text{otherwise}\end{cases}</code></pre>
<p>对于合约创建交易，<code class="katex-inline">T_{\mathrm{t}}</code> 是 空字节 的 RLP</p>
<h3>4.3 The Block 区块</h3>
<p><code class="katex-inline">Block</code></p>
<ul>
<li><code class="katex-inline">H</code>
<ul>
<li>当前区块的区块头</li>
</ul>
</li>
<li><code class="katex-inline">\mathbf{T}</code>
<ul>
<li>当前区块内的一系列交易</li>
</ul>
</li>
<li><code class="katex-inline">\mathbf{U}</code>
<ul>
<li>当前区块内的叔块头列表</li>
</ul>
</li>
</ul>
<p><code class="katex-inline">H</code> (block header)</p>
<ul>
<li><code class="katex-inline">H_{\mathrm{p}}</code>
<ul>
<li>parentHash</li>
<li>父区块 block header 的 hash</li>
</ul>
</li>
<li><code class="katex-inline">H_{\mathrm{o}}</code>
<ul>
<li>ommersHash</li>
<li>当前区块的叔块列表的 hash</li>
</ul>
</li>
<li><code class="katex-inline">H_{\mathrm{c}}</code>
<ul>
<li>beneficiary</li>
<li>因为挖到当前区块而获得奖励收益的账户地址</li>
</ul>
</li>
<li><code class="katex-inline">H_{\mathrm{r}}</code>
<ul>
<li>stateRoot
<ul>
<li>state trie 根节点的 hash
<ul>
<li>交易被执行完且区块定稿后的状态</li>
<li>区块内的所有交易得到的状态，组成一棵树</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><code class="katex-inline">H_{\mathrm{t}}</code>
<ul>
<li>transactionsRoot
<ul>
<li>transaction trie 根节点的 hash
<ul>
<li>当前区块中所有交易组成的一棵树<br />
<code class="katex-inline">H_{\mathrm{e}}</code></li>
</ul>
</li>
</ul>
</li>
<li>receiptsRoot
<ul>
<li>receipt trie 根节点的 has
<ul>
<li>当前区块中所有交易的收据组成的一棵树</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><code class="katex-inline">H_{\mathrm{b}}</code>
<ul>
<li>logsBloom</li>
<li>当前区块中所有交易的收据数据中的可索引信息(产生日志的地址和日志主题)组成的 Bloom 过滤器</li>
</ul>
</li>
<li><code class="katex-inline">H_{\mathrm{d}}</code>
<ul>
<li>difficulty</li>
<li>含义<br />
当前区块的难度水平<br />
根据前一个区块的难度水平和时间戳计算得到</li>
</ul>
</li>
<li><code class="katex-inline">H_{\mathrm{i}}</code>
<ul>
<li>number</li>
<li>当前区块的祖先的数量</li>
</ul>
</li>
<li><code class="katex-inline">H_{\mathrm{l}}</code>
<ul>
<li>gasLimit</li>
<li>目前每个区块的 gas 开支上限</li>
</ul>
</li>
<li><code class="katex-inline">H_{\mathrm{g}}</code>
<ul>
<li>gasUsed</li>
<li>当前区块中所有交易所用掉的 gas 之和</li>
</ul>
</li>
<li><code class="katex-inline">H_{\mathrm{s}}</code>
<ul>
<li>timestamp</li>
<li>当前区块初始化时的 Unix 时间戳</li>
</ul>
</li>
<li><code class="katex-inline">H_{\mathrm{x}}</code>
<ul>
<li>extraData</li>
<li>含义
<ul>
<li>与当前区块相关的任意字节数据</li>
<li>必须在 32 字节以内</li>
</ul>
</li>
</ul>
</li>
<li><code class="katex-inline">H_{\mathrm{m}}</code>
<ul>
<li>mixHash</li>
<li>含义
<ul>
<li>一个 hash 值</li>
<li>用于与 <code class="katex-inline">H_{\mathrm{n}}</code> 一起证明当前区块已经承载了足够的计算量</li>
</ul>
</li>
</ul>
</li>
<li><code class="katex-inline">H_{\mathrm{n}}</code>
<ul>
<li>nonce</li>
<li>含义
<ul>
<li>一个64位的随机整数</li>
<li>用于与 <code class="katex-inline">H_{\mathrm{m}}</code> 一起证明当前区块已经承载了足够的计算量</li>
</ul>
</li>
<li>注意
<ul>
<li>这里的 nonce 是个随机数，与账户下的 nonce 定义不同</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>公式 (20)</p>
<pre><code class="language-katex">B \quad\equiv\quad (B_{\mathrm{H}}, B_{\mathbf{T}}, B_{\mathbf{U}})</code></pre>
<h4>4.3.1 Transaction Receipt 交易收据</h4>
<p>每个交易一定会有一条对应的收据</p>
<p><code class="katex-inline">R</code></p>
<ul>
<li>Transaction Receipt</li>
<li>含义
<ul>
<li>每个交易执行过程中的特定信息，会被编码为交易收据</li>
</ul>
</li>
<li>用途
<ul>
<li>零知识证明</li>
<li>索引</li>
<li>搜索</li>
</ul>
</li>
<li><code class="katex-inline">B_{\mathbf{R}}[i]</code>
<ul>
<li>当前区块第 i 个交易的收据</li>
</ul>
</li>
</ul>
<p><code class="katex-inline">H_{\mathrm{e}}</code></p>
<ul>
<li>receiptsRoot</li>
<li>含义
<ul>
<li>区块内的收据组成 receipt trie</li>
<li>其根节点的 hash 存储在 <code class="katex-inline">H_{\mathrm{e}}</code> 中</li>
</ul>
</li>
</ul>
<p>公式 (21)</p>
<pre><code class="language-katex">R \quad\equiv\quad (R_{\mathrm{z}}, R_{\mathrm{u}}, R_{\mathrm{b}}, R_{\mathbf{l}})</code></pre>
<p><code class="katex-inline">R</code> 一条收据的组成</p>
<ul>
<li><code class="katex-inline">R_{\mathrm{u}}</code>
<ul>
<li>当前区块中，交易发生后的累积 gas 使用量</li>
</ul>
</li>
<li><code class="katex-inline">R_{\mathrm{l}}</code>
<ul>
<li>交易过程中创建的一系列日志</li>
</ul>
</li>
<li><code class="katex-inline">R_{\mathrm{b}}</code>
<ul>
<li>256字节大小的 hash</li>
<li>由一系列日志构成的 Bloom 过滤器</li>
</ul>
</li>
<li><code class="katex-inline">R_{\mathrm{z}}</code>
<ul>
<li>交易的状态码</li>
</ul>
</li>
</ul>
<p>公式 (22)</p>
<pre><code class="language-katex">R_{\mathrm{z}} \in \mathbb{N}</code></pre>
<p>公式 (23)</p>
<pre><code class="language-katex">R_{\mathrm{u}} \in \mathbb{N} \quad \wedge \quad R_{\mathrm{b}} \in \mathbb{B}_{256}</code></pre>
<p>公式 (24)</p>
<pre><code class="language-katex">O \quad\equiv\quad (O_{\mathrm{a}}, ({O_{\mathbf{t}}}_0, {O_{\mathbf{t}}}_1, ...), O_{\mathbf{d}})</code></pre>
<p>只有被调用的智能合约，自身显式调用 <code>LOG0</code>，<code>LOG1</code>，<code>LOG2</code>，<code>LOG3</code>，<code>LOG4</code> 才会生成日志</p>
<p><code class="katex-inline">R_{\mathrm{l}}</code></p>
<ul>
<li>由一系列日志组成</li>
<li><code class="katex-inline">(O_0,O_1,...)</code></li>
</ul>
<p><code class="katex-inline">O</code> 一条日志的组成</p>
<ul>
<li><code class="katex-inline">O_{\mathrm{a}}</code>
<ul>
<li>当前被调用的智能合约的地址 (一定不会包括 EOA 地址)</li>
<li>跟随消息调用上下文变化</li>
<li>例子
<ul>
<li>EOA -&gt; ContracA -&gt; ContractB -&gt; ContractC</li>
</ul>
</li>
</ul>
</li>
<li><code class="katex-inline">O_{\mathrm{t}}</code>
<ul>
<li>一系列日志主题</li>
</ul>
</li>
<li><code class="katex-inline">O_{\mathrm{d}}</code>
<ul>
<li>日志数据</li>
</ul>
</li>
</ul>
<pre><code class="language-go">// github.com/ethereum/go-ethereum@v1.10.6/core/vm/instructions.go

// make log instruction function
func makeLog(size int) executionFunc {
    return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
        topics := make([]common.Hash, size)
        stack := scope.Stack
        mStart, mSize := stack.pop(), stack.pop()
        for i := 0; i &lt; size; i++ {
            addr := stack.pop()
            topics[i] = addr.Bytes32()
        }

        d := scope.Memory.GetCopy(int64(mStart.Uint64()), int64(mSize.Uint64()))
        interpreter.evm.StateDB.AddLog(&amp;types.Log{
            Address: scope.Contract.Address(),
            Topics:  topics,
            Data:    d,
            // This is a non-consensus field, but assigned here because
            // core/state doesn&#039;t know the current block number.
            BlockNumber: interpreter.evm.Context.BlockNumber.Uint64(),
        })

        return nil, nil
    }
}</code></pre>
<p>公式 (25)</p>
<pre><code class="language-katex">O_{\mathrm{a}} \in \mathbb{B}_{20} \quad \wedge \quad \forall x \in O_{\mathbf{t}}: x \in \mathbb{B}_{32} \quad \wedge \quad O_{\mathbf{d}} \in \mathbb{B}</code></pre>
<p>公式 (26)</p>
<p><code class="katex-inline">M</code> 函数</p>
<ul>
<li>定义
<ul>
<li>计算一条日志 <code class="katex-inline">O</code> 的摘要函数</li>
</ul>
</li>
<li>输入
<ul>
<li><code class="katex-inline">{x \in \{O_{\mathrm{a}}\} \cup O_{\mathbf{t}}}</code>
<ul>
<li>取并集
<ul>
<li>日志地址 <code class="katex-inline">O_{\mathrm{a}}</code>
<ul>
<li>转成集合 <code class="katex-inline">\{O_{\mathrm{a}}\}</code></li>
</ul>
</li>
<li>一系列日志主题
<ul>
<li><code class="katex-inline">{O_{\mathrm{t}}}</code></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>输出
<ul>
<li>256字节的哈希</li>
</ul>
</li>
</ul>
<pre><code class="language-katex">M(O) \quad\equiv\quad {\bigvee}_{x \in \{O_{\mathrm{a}}\} \cup O_{\mathbf{t}}} \big( M_{3:2048}(x) \big)</code></pre>
<p>理解如下</p>
<p><code class="katex-inline">M_{3:2048}</code> 是个大小为256字节，共2048位的 Bloom 过滤器</p>
<ul>
<li>对于输入数据，计算它的 Keccak-256 哈希值</li>
<li>取哈希值的前6个字节，两两组成一对
<ul>
<li>一对字节有16个比特</li>
<li>一共有3对</li>
</ul>
</li>
<li>遍历这3对字节
<ul>
<li>取低11位，得到索引，则索引范围是 [0, 2047](2^11 == 2048)</li>
<li>设置 Bloom 过滤器对应索引的位为1</li>
</ul>
</li>
<li>因此
<ul>
<li>对于输入数据，会设置过滤器的随机3位</li>
</ul>
</li>
</ul>
<p>公式 (27) (28) (29) (30)</p>
<pre><code class="language-katex">\begin{aligned}
M_{3:2048}(\mathbf{x}: \mathbf{x} \in \mathbb{B}) &amp; \quad\equiv\quad \mathbf{y}: \mathbf{y} \in \mathbb{B}_{256} \quad \text{where:} \\
\mathbf{y} &amp; \quad=\quad (0, 0, ..., 0) \quad \text{except:} \\
\forall i \in \{0, 2, 4\} &amp; \quad:\quad \mathcal{B}_{m(\mathbf{x}, i)}(\mathbf{y}) = 1 \\
m(\mathbf{x}, i) &amp; \quad\equiv\quad \mathtt{KEC}(\mathbf{x})[i, i + 1] \bmod 2048
\end{aligned}</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">\mathcal{B}</code> 表示位引用函数</li>
<li><code class="katex-inline">\mathcal{B}_{\mathrm{j}}(\mathbf{x}) = 1</code> 表示设置字节数组 <code class="katex-inline">\mathbf{x}</code> 中的第 j 位(从0开始) 为1</li>
</ul>
<p>如果仅看黄皮书中关于几个 <code>LOG$</code> 的 opcode 说明，会误以为过滤器仅包含日志，容易对使用场景产生困惑。实际上，在 go-ethereum 实现中，会把产生日志的合约地址 <code>log.Address</code> 也记录在过滤器</p>
<pre><code class="language-go">// github.com/ethereum/go-ethereum@v1.10.6/core/types/bloom9.go

// CreateBloom creates a bloom filter out of the give Receipts (+Logs)
func CreateBloom(receipts Receipts) Bloom {
    buf := make([]byte, 6)
    var bin Bloom
    for _, receipt := range receipts {
        for _, log := range receipt.Logs {
            bin.add(log.Address.Bytes(), buf)
            for _, b := range log.Topics {
                bin.add(b[:], buf)
            }
        }
    }
    return bin
}</code></pre>
<h4>4.3.2 Holistic Validity 整体有效性</h4>
<p>公式 (31)</p>
<pre><code class="language-katex">\begin{aligned}
H_{\mathrm{r}} &amp; \quad\equiv\quad \mathtt{TRIE}(L_S(\Pi(\boldsymbol{\sigma}, B))) &amp; \quad\quad\wedge \\
H_{\mathrm{o}} &amp; \quad\equiv\quad \mathtt{KEC}(\mathtt{RLP}(L_H^*(B_{\mathbf{U}}))) &amp; \quad\quad\wedge \\
H_{\mathrm{t}} &amp; \quad\equiv\quad \mathtt{TRIE}(\{\forall i &lt; \lVert B_{\mathbf{T}} \rVert, i \in \mathbb{N}: p (i, L_{\mathrm{T}}(B_{\mathbf{T}}[i]))\}) &amp; \quad\quad\wedge \\
H_{\mathrm{e}} &amp; \quad\equiv\quad \mathtt{TRIE}(\{\forall i &lt; \lVert B_{\mathbf{R}} \rVert, i \in \mathbb{N}: p(i, B_{\mathbf{R}}[i])\}) &amp; \quad\quad\wedge \\
H_{\mathrm{b}} &amp; \quad\equiv\quad {\bigvee}_{\mathbf{r} \in B_{\mathbf{R}}} \big( \mathbf{r}_{\mathrm{b}} \big)
\end{aligned}</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">H_{\mathrm{r}}</code>
<ul>
<li>storageRoot</li>
</ul>
</li>
<li><code class="katex-inline">H_{\mathrm{o}}</code>
<ul>
<li>ommersHash</li>
</ul>
</li>
<li><code class="katex-inline">H_{\mathrm{t}}</code>
<ul>
<li>transactionsRoot</li>
</ul>
</li>
<li><code class="katex-inline">H_{\mathrm{e}}</code>
<ul>
<li>receiptsRoot</li>
</ul>
</li>
<li><code class="katex-inline">H_{\mathrm{b}}</code>
<ul>
<li>logsBloom</li>
</ul>
</li>
</ul>
<p>其中 公式 (32)</p>
<pre><code class="language-katex">p(k, v) \quad\equiv\quad \big( \mathtt{RLP}(k), \mathtt{RLP}(v) \big)</code></pre>
<p>而且 公式 (33)</p>
<pre><code class="language-katex">\mathtt{TRIE}(L_{\mathrm{S}}(\boldsymbol{\sigma})) \quad=\quad {P(B_H)_H}_{\mathrm{r}}</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">P(B_H)</code>
<ul>
<li>表示区块 B 的父区块</li>
</ul>
</li>
<li><code class="katex-inline">\Pi(\boldsymbol{\sigma}, B))</code>
<ul>
<li>参考公式 (4)</li>
<li>参考公式 (177)</li>
</ul>
</li>
<li><code class="katex-inline">L_S</code>
<ul>
<li>世界状态坍塌函数</li>
<li>参考公式 (10)</li>
</ul>
</li>
<li><code class="katex-inline">L_{\mathrm{H}}^*</code>
<ul>
<li>参考公式 (34)和(36))</li>
</ul>
</li>
<li><code class="katex-inline">L_{\mathrm{T}}</code>
<ul>
<li>参考公式 (16)</li>
</ul>
</li>
</ul>
<h4>4.3.3 Serialization 序列化</h4>
<p>公式 (34) 定义 H 的序列化函数 <code class="katex-inline">L_{\mathrm{H}}</code> 如下</p>
<pre><code class="language-katex">L_{\mathrm{H}}(H) \quad\equiv\quad (H_{\mathrm{p}}, H_{\mathrm{o}}, H_{\mathrm{c}}, H_{\mathrm{r}}, H_{\mathrm{t}}, H_{\mathrm{e}}, H_{\mathrm{b}}, H_{\mathrm{d}}, H_{\mathrm{i}}, H_{\mathrm{l}}, H_{\mathrm{g}}, H_{\mathrm{s}}, H_{\mathrm{x}}, H_{\mathrm{m}}, H_{\mathrm{n}} \; )</code></pre>
<p>公式 (35) 则 B 的序列化函数 <code class="katex-inline">L_{\mathrm{B}}</code> 为</p>
<pre><code class="language-katex">L_{\mathrm{B}}(B) \quad\equiv\quad \big( L_{\mathrm{H}}(B_{\mathrm{H}}), L_{\mathrm{T}}^*(B_{\mathbf{T}}), L_{\mathrm{H}}^*({B_{\mathbf{U}}}) \big)</code></pre>
<p>公式 (36)</p>
<p>其中</p>
<ul>
<li><code class="katex-inline">L_{\mathrm{T}}</code> 参考公式 (16)</li>
<li><code class="katex-inline">L_{\mathrm{H}}</code> 参考公式 (34)</li>
</ul>
<p><code class="katex-inline">L_{\mathrm{T}}^*</code> 和 <code class="katex-inline">L_{\mathrm{H}}^*</code> 分别是它们的 <code>reduce</code> 函数</p>
<p><code>reduce</code> 函数定义如下: 对集合中的每一个元素，分别执行指定函数，得到的结果组成一个集合</p>
<pre><code class="language-katex">{f^*}\big( (x_0, x_1, ...) \big) \quad\equiv\quad \big( f(x_0), f(x_1), ... \big) \quad \text{for any function} \; f</code></pre>
<p>公式 (37) 值域/约束</p>
<pre><code class="language-katex">\begin{aligned}
&amp; {H_{\mathrm{p}}} \in \mathbb{B}_{32} &amp; \quad\wedge\quad &amp; H_{\mathrm{o}} \in \mathbb{B}_{32} &amp; \quad\wedge\quad &amp; H_{\mathrm{c}} \in \mathbb{B}_{20} &amp; \quad\wedge\quad \\
&amp; {H_{\mathrm{r}}} \in \mathbb{B}_{32} &amp; \quad\wedge\quad &amp; H_{\mathrm{t}} \in \mathbb{B}_{32} &amp; \quad\wedge\quad &amp; {H_{\mathrm{e}}} \in \mathbb{B}_{32} &amp; \quad\wedge\quad \\
&amp; {H_{\mathrm{b}}} \in \mathbb{B}_{256} &amp; \quad\wedge\quad &amp; H_{\mathrm{d}} \in \mathbb{N} &amp; \quad\wedge\quad &amp; {H_{\mathrm{i}}} \in \mathbb{N} &amp; \quad\wedge\quad \\
&amp; {H_{\mathrm{l}}} \in \mathbb{N} &amp; \quad\wedge\quad &amp; H_{\mathrm{g}} \in \mathbb{N} &amp; \quad\wedge\quad &amp; {H_{\mathrm{s}}} \in \mathbb{N}_{256} &amp; \quad\wedge\quad \\
&amp; {H_{\mathrm{x}}} \in \mathbb{B} &amp; \quad\wedge\quad &amp; H_{\mathrm{m}} \in \mathbb{B}_{32} &amp; \quad\wedge\quad &amp; {H_{\mathrm{n}}} \in \mathbb{B}_{8}
\end{aligned}</code></pre>
<p>公式 (38)</p>
<pre><code class="language-katex">\mathbb{B}_{\mathrm{n}} = \{ B: B \in \mathbb{B} \wedge \lVert B \rVert = n \}</code></pre>
<h3>4.3.4 Block Header Validity 区块头验证</h3>
<p>公式 (39) 根据区块头 H 找到其父区块</p>
<pre><code class="language-katex">P(H) \equiv B&#039;: \mathtt{KEC}(\mathtt{RLP}(B&#039;_{\mathrm{H}})) = H_{\mathrm{p}}</code></pre>
<p>公式 (40) 区块头 H 中的区块编号(block number)计算方式为</p>
<pre><code class="language-katex">H_{\mathrm{i}} \equiv {{P(H)_{\mathrm{H}}}_{\mathrm{i}}} + 1</code></pre>
<p>公式 (41) 根据区块头 H 计算权威难度值的公式</p>
<pre><code class="language-katex">D(H) \equiv \begin{dcases}
{D_0} &amp; \text{if} \quad H_{\mathrm{i}} = 0\\
\text{max}\!\left({D_0}, {P(H)_{\mathrm{H}}}_{\mathrm{d}} + x\times\varsigma_2 + \epsilon \right) &amp; \text{otherwise}\\
\end{dcases}</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">{P(H)_{\mathrm{H}}}_{\mathrm{d}}</code>
<ul>
<li>以父块难度作为难度调整基数</li>
</ul>
</li>
<li><code class="katex-inline">x\times\varsigma_2</code>
<ul>
<li>用于自适应难度调整，维持稳定的出块速度</li>
</ul>
</li>
<li><code class="katex-inline">\epsilon</code>
<ul>
<li>表示设定的难度炸弹</li>
</ul>
</li>
</ul>
<p>--</p>
<p>注意两个函数，黄皮书与具体实现存在出入，主要是在 <code class="katex-inline">\epsilon</code> 的处理上与公式 (41) 不同，实现为</p>
<pre><code class="language-katex">D(H) \equiv \begin{dcases}
{D_0} &amp; \text{if} \quad H_{\mathrm{i}} = 0\\
\text{max}\!\left({D_0}, {P(H)_{\mathrm{H}}}_{\mathrm{d}} + x\times\varsigma_2 \right) + \epsilon &amp; \text{otherwise}\\
\end{dcases}</code></pre>
<pre><code class="language-go">// github.com/ethereum/go-ethereum@v1.10.6/consensus/ethash/consensus.go
func makeDifficultyCalculator(bombDelay *big.Int) func(time uint64, parent *types.Header) *big.Int;

// github.com/ethereum/go-ethereum@v1.10.6/consensus/ethash/difficulty.go
func MakeDifficultyCalculatorU256(bombDelay *big.Int) func(time uint64, parent *types.Header) *big.Int;</code></pre>
<p>公式 (42) 创世区块的难度值</p>
<pre><code class="language-katex">{D_0} \equiv 131072</code></pre>
<p>$2^{17} = 131072$</p>
<p>公式 (43) 难度值调整的单位</p>
<pre><code class="language-katex">x \equiv \left\lfloor\frac{{P(H)_{\mathrm{H}}}_{\mathrm{d}}}{2048}\right\rfloor</code></pre>
<p>公式 (44) 难度值调整的系数</p>
<pre><code class="language-katex">\varsigma_2 \equiv \text{max}\left(y - \left\lfloor\frac{H_{\mathrm{s}} - {P(H)_{\mathrm{H}}}_{\mathrm{s}}}{9}\right\rfloor, -99 \right)</code></pre>
<pre><code class="language-katex">y \equiv \begin{cases}
1 &amp; \text{if} \, \lVert P(H)_{\mathbf{U}}\rVert = 0 \\
2 &amp; \text{otherwise}
\end{cases}</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">y</code>
<ul>
<li>如果父区块中包含叔父块，则难度调整会大一个单位 (从1调整为2)</li>
<li>因为包含叔父块时发行的货币量大，需要适当提高难度以保持货币发行量稳定</li>
</ul>
</li>
<li><code class="katex-inline">-99</code>
<ul>
<li>难度一次最多调整 <code class="katex-inline">-99</code> 个单位</li>
<li>主要是应对被黑客攻击或其他目前想不到的黑天鹅事件</li>
</ul>
</li>
<li><code class="katex-inline">y - \left\lfloor\frac{H_{\mathrm{s}} - {P(H)_{\mathrm{H}}}_{\mathrm{s}}}{9}\right\rfloor</code>
<ul>
<li><code class="katex-inline">{H_{\mathrm{s}}}</code>
<ul>
<li>本区块的时间戳，以秒为单位</li>
</ul>
</li>
<li><code class="katex-inline">{P(H)_{\mathrm{H}}}_{\mathrm{s}}</code>
<ul>
<li>父区块的时间戳，以秒为单位</li>
</ul>
</li>
<li>规定
<ul>
<li><code class="katex-inline">{H_{\mathrm{s}}} \gt {P(H)_{\mathrm{H}}}_{\mathrm{s}}</code></li>
</ul>
</li>
<li>自适应调整
<ul>
<li>出块时间过短则调大难度</li>
<li>出块时间过长则调小难度</li>
</ul>
</li>
<li>例子
<ul>
<li>出块时间在 [1,8] 之间
<ul>
<li>出块时间过短</li>
<li>难度调大一个单位</li>
</ul>
</li>
<li>出块时间在 [9,17] 之间
<ul>
<li>出块时间可以接受</li>
<li>难度保持不便</li>
</ul>
</li>
<li>出块时间在 [18,26] 之间
<ul>
<li>出块时间过长</li>
<li>难度调小一个单位</li>
</ul>
</li>
<li>...</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>公式 (45) (46) (47) 难度炸弹</p>
<pre><code class="language-katex">\epsilon \equiv \left\lfloor 2^{ \left\lfloor H&#039;_{\mathrm{i}} \div 100000 \right\rfloor - 2 } \right\rfloor \\</code></pre>
<p>其中</p>
<pre><code class="language-katex">H&#039;_{\mathrm{i}} \equiv \max(H_{\mathrm{i}} - \kappa, 0) \\</code></pre>
<p>其中</p>
<pre><code class="language-katex">\kappa \equiv \begin{cases}
  3000000 &amp; \text{if} \quad F_{\mathrm{Byzantium}} \leqslant H_{\mathrm{i}} &lt; F_{\mathrm{Constantinople}} \\
  5000000 &amp; \text{if} \quad F_{\mathrm{Constantinople}} \leqslant H_{\mathrm{i}} &lt; F_{\mathrm{Muir Glacier}} \\
  9000000 &amp; \text{if} \quad H_{\mathrm{i}} \geqslant F_{\mathrm{Muir Glacier}} \\
\end{cases}</code></pre>
<pre><code class="language-katex">\begin{aligned}
F_{\mathrm{Homestead}} &amp; \equiv 1150000 \\
F_{\mathrm{Byzantium}} &amp; \equiv 4370000 \\
F_\mathrm{Constantinople} &amp; \equiv 7280000 \\
F_{\mathrm{Muir Glacier}} &amp; \equiv 9200000
\end{aligned}</code></pre>
<p>为什么设置难度炸弹？</p>
<ul>
<li>降低迁移到 PoS 协议时发生 fork 的风险</li>
<li>到时挖矿难度非常大，矿工将被迫迁移到 PoS 协议</li>
</ul>
<p>参数</p>
<ul>
<li><code class="katex-inline">\epsilon</code>
<ul>
<li>是2的指数函数，每十万个块扩大一倍</li>
<li>后期增长非常快 (&quot;炸弹&quot;)</li>
</ul>
</li>
<li><code class="katex-inline">H'_{\mathrm{i}}</code>
<ul>
<li>低估了 PoS 协议的开发难度，导致一再延迟</li>
<li>炸弹威力显现后，通过假区块号(回退区块号)来降低难度
<ul>
<li>同时把区块奖励从5个 ETH 降为3个 ETH</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><img decoding="async" src="https://godorz.info/wp-content/uploads/2021/10/difficulity_bomb.png" alt="Difficulity Bomb" /></p>
<p>参考 <a href="https://docs.ethhub.io/questions-about-ethereum/what-is-the-difficulty-bomb/">what-is-the-difficulty-bomb</a></p>
<p>公式 (48) gasLimit 约束</p>
<pre><code class="language-katex">H_{\mathrm{l}} &lt; {P(H)_{\mathrm{H}}}_{\mathrm{l}} + \left\lfloor\frac{{P(H)_{\mathrm{H}}}_{\mathrm{l}}}{1024}\right\rfloor \quad \wedge \\
H_{\mathrm{l}} &gt; {P(H)_{\mathrm{H}}}_{\mathrm{l}} - \left\lfloor\frac{{P(H)_{\mathrm{H}}}_{\mathrm{l}}}{1024}\right\rfloor \quad \wedge \\
H_{\mathrm{l}} \geqslant 5000</code></pre>
<p>公式 (49) timestamp 约束</p>
<pre><code class="language-katex">H_{\mathrm{s}} &gt; {P(H)_{\mathrm{H}}}_{\mathrm{s}}</code></pre>
<p>公式 (41)难度计算公式的设计，保证了难度根据出块间隔长短的动态平衡</p>
<ul>
<li>如果最近的两个区块间隔较短，则会导致难度值增加，因此需要额外的计算量，大概率会延长下个区块的出块时间</li>
<li>相反，如果最近的两个区块间隔过长，难度值和下一个区块的预期出块时间也会减少</li>
</ul>
<p>公式 (50) nonce / mixHash 约束</p>
<p>必须同时满足</p>
<pre><code class="language-katex">n \leqslant \frac {2^{256}}{H_{\mathrm{d}}} \quad \wedge \quad m = H_{\mathrm{m}} \\
with \quad (n, m) = \mathtt{PoW}(H_{\cancel{n}}, H_{\mathrm{n}}, \mathbf{d})</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">H_{\cancel{n}}</code>
<ul>
<li>当前区块 header <code class="katex-inline">H</code></li>
<li>但不包含 nonce 和 mixHash components</li>
</ul>
</li>
<li><code class="katex-inline">\mathbf{d}</code>
<ul>
<li>当前 DAG</li>
<li>用于计算 mixHash <code class="katex-inline">H_{\mathrm{m}}</code></li>
</ul>
</li>
<li><code class="katex-inline">\mathtt{PoW}</code>
<ul>
<li>工作量证明函数</li>
<li>保证<br />
除了列举所有的可能性，没有更好的其他方法来找到一个低于要求阈值的 nonce<br />
攻击者必须拥有超过网络一半的算力，才能比其他人更快地找到 nonce</li>
</ul>
</li>
</ul>
<p>公式 (51)</p>
<p>综上所述，block header 的验证函数 <code class="katex-inline">V(H)</code> 定义如下：</p>
<pre><code class="language-katex">\begin{aligned}
V(H) \equiv\quad &amp; n \leqslant \frac{2^{256}}{H_{\mathrm{d}}} \wedge m = H_{\mathrm{m}} \quad &amp;\wedge \\
&amp; H_{\mathrm{d}} = D(H) \quad &amp;\wedge \\
&amp; H_{\mathrm{g}} \le H_{\mathrm{l}}  \quad &amp;\wedge \\
&amp; H_{\mathrm{l}} &lt; {P(H)_{\mathrm{H}}}_{\mathrm{l}} + \left\lfloor\frac{{P(H)_{\mathrm{H}}}_{\mathrm{l}}}{1024}\right\rfloor  \quad &amp;\wedge \\
&amp; H_{\mathrm{l}} &gt; {P(H)_{\mathrm{H}}}_{\mathrm{l}} - \left\lfloor\frac{{P(H)_{\mathrm{H}}}_{\mathrm{l}}}{1024}\right\rfloor  \quad &amp;\wedge \\
&amp; H_{\mathrm{l}} \geqslant 5000  \quad &amp;\wedge \\
&amp; H_{\mathrm{s}} &gt; {P(H)_{\mathrm{H}}}_{\mathrm{s}} \quad &amp;\wedge \\
&amp; H_{\mathrm{i}} = {P(H)_{\mathrm{H}}}_{\mathrm{i}} +1 \quad &amp;\wedge \\
&amp; \lVert H_{\mathrm{x}} \rVert \le 32 \\
where \quad &amp; (n, m) = \mathtt{PoW}(H_{\cancel{n}}, H_{\mathrm{n}}, \mathbf{d})
\end{aligned}</code></pre>
<p>此外，<code class="katex-inline">\textbf{extraData}</code> 最多为32字节</p>
<h2>5. Gas and Payment Gas 与支付</h2>
<p>一般来说，用于支付交易的 gas 费用，会被发往 beneficiary 地址，这个地址由矿工设置</p>
<h2>6. Transaction Execution 交易执行</h2>
<p><code class="katex-inline">\Upsilon</code> 交易状态转换函数</p>
<p><code class="katex-inline">\Upsilon</code> 在执行前，检查如下条件:</p>
<ol>
<li>交易以 RLP 格式正确编码，且没有尾随多余的数据</li>
<li>交易签名正确</li>
<li>nonce 有效(即等于交易发送者账户当前的 nonce)</li>
<li>gas limit 不小于 <code class="katex-inline">g_0</code> (计算公式参考(55) (56) (57))</li>
<li>发送者账户 balance 不小于实际费用 <code class="katex-inline">v_0</code> (计算公式参考(58))</li>
</ol>
<p>公式 (52) 交易状态转换函数</p>
<pre><code class="language-katex">\boldsymbol{\sigma}&#039; = \Upsilon(\boldsymbol{\sigma}, T)</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">\boldsymbol{\sigma}'</code>
<ul>
<li>交易完成后的世界状态</li>
</ul>
</li>
<li><code class="katex-inline">\Upsilon^{\mathrm{g}}</code>
<ul>
<li>交易实际消耗的 gas</li>
</ul>
</li>
<li><code class="katex-inline">\Upsilon^{\mathbf{l}}</code>
<ul>
<li>交易执行时产生的一系列日志</li>
</ul>
</li>
<li><code class="katex-inline">\Upsilon^{\mathrm{z}}</code>
<ul>
<li>交易返回的状态码</li>
</ul>
</li>
</ul>
<h3>6.1 Substrate 子状态</h3>
<p>公式 (53) 交易执行过程中产生的子状态</p>
<pre><code class="language-katex">A \equiv (A_{\mathbf{s}}, A_{\mathbf{l}}, A_{\mathbf{t}}, A_{\mathrm{r}})</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">A_{\mathbf{s}}</code>
<ul>
<li>自毁集合
<ul>
<li>交易执行完成后会被销毁的账户</li>
</ul>
</li>
</ul>
</li>
<li><code class="katex-inline">A_{\mathbf{l}}</code>
<ul>
<li>一系列日志
<ul>
<li>方便外界旁观者简单跟踪合约调用</li>
</ul>
</li>
</ul>
</li>
<li><code class="katex-inline">A_{\mathbf{t}}</code>
<ul>
<li>交易接触的账户
<ul>
<li>其中的 EMPTY 账户会被删除</li>
</ul>
</li>
</ul>
</li>
<li><code class="katex-inline">A_{\mathbf{r}}</code>
<ul>
<li>refund balance
<ul>
<li>累计需要归还的 gas</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>公式 (54) 空的交易子状态</p>
<pre><code class="language-katex">A^0 \equiv (\varnothing,(), \varnothing, 0)</code></pre>
<h3>6.2 Execution 执行</h3>
<p>状态转换</p>
<ul>
<li><code class="katex-inline">\boldsymbol{\sigma}</code> 原始状态</li>
<li><code class="katex-inline">\boldsymbol{\sigma}_0</code> 检查点状态
<ul>
<li>发送者 balance 扣除 <code class="katex-inline">{T_g}{T_p}</code></li>
<li>发送者 nonce 累加</li>
</ul>
</li>
<li><code class="katex-inline">\boldsymbol{\sigma}_{\mathrm{P}}</code> 执行交易后临时状态
<ul>
<li>\//</li>
</ul>
</li>
<li><code class="katex-inline">\boldsymbol{\sigma}^*</code> 预备最终状态
<ul>
<li>剩余的 gas 返回给发送者</li>
<li>消耗的 gas 发给 beneficiary(由打包区块的矿工指定)</li>
</ul>
</li>
<li><code class="katex-inline">\boldsymbol{\sigma}'</code> 最终状态
<ul>
<li>删除自毁集合账户</li>
<li>删除接触账户集合中的空账户</li>
</ul>
</li>
</ul>
<p>--</p>
<p>公式 (55) (56) (57) <code class="katex-inline">g_0</code> 交易执行前预付的基础 gas</p>
<pre><code class="language-katex">g_0 \equiv {}
\sum_{i \in T_{\mathbf{i}}, T_{\mathbf{d}}} \begin{cases} G_{\mathrm{txdatazero}} &amp;\text{if} \quad i = 0 \\ G_{\mathrm{txdatanonzero}} &amp;\text{otherwise} \end{cases}
\quad + \quad \begin{cases} G_{\mathrm{txcreate}} &amp; \text{if} \quad T_{\mathrm{t}} = \varnothing \\ 0 &amp; \text{otherwise} \end{cases}
\quad + \quad G_{\mathrm{transaction}}</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">T_{\mathbf{i}}</code>
<ul>
<li>合约创建的 initcode</li>
</ul>
</li>
<li><code class="katex-inline">T_{\mathbf{d}}</code>
<ul>
<li>消息调用的 inputdata</li>
</ul>
</li>
<li><code class="katex-inline">G_{\mathrm{txcreate}}</code>
<ul>
<li>如果是合约创建，需要计入成本</li>
</ul>
</li>
</ul>
<p>公式 (58) <code class="katex-inline">v_0</code> 交易执行前预付的费用</p>
<pre><code class="language-katex">v_0 \quad\equiv\quad T_{\mathrm{g}} T_{\mathrm{p}} + T_{\mathrm{v}}</code></pre>
<p>公式 (59)</p>
<pre><code class="language-katex">\begin{aligned}
S(T) &amp; \quad\neq\quad \varnothing \quad \wedge \\
\boldsymbol{\sigma}[S(T)] &amp; \quad\neq\quad \varnothing \quad \wedge \\
T_{\mathrm{n}} &amp; \quad=\quad \boldsymbol{\sigma}[S(T)]_{\mathrm{n}} \quad \wedge \\
g_0 &amp; \quad\leqslant\quad T_{\mathrm{g}} \quad \wedge \\
v_0 &amp; \quad\leqslant\quad \boldsymbol{\sigma}[S(T)]_{\mathrm{b}} \quad \wedge \\
T_{\mathrm{g}} &amp; \quad\leqslant\quad {B_{\mathrm{H}}}_{\mathrm{l}} - {\ell}(B_{\mathbf{R}})_{\mathrm{u}}
\end{aligned}</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">S(T)</code>
<ul>
<li>根据交易查找其发送者的函数</li>
</ul>
</li>
<li><code class="katex-inline">T_{\mathrm{g}}</code>
<ul>
<li>交易的 gasLImit</li>
</ul>
</li>
<li><code class="katex-inline">{\ell}(B_{\mathbf{R}})_{\mathrm{u}}</code>
<ul>
<li>当前区块累计已经消耗的 gas</li>
</ul>
</li>
<li><code class="katex-inline">{B_{\mathrm{H}}}_{\mathrm{l}}</code>
<ul>
<li>当前区块的 gasLimit</li>
</ul>
</li>
</ul>
<p>公式 (60) (61) (62) 检查点状态 <code class="katex-inline">\boldsymbol{\sigma}_0</code></p>
<pre><code class="language-katex">\begin{aligned}
\boldsymbol{\sigma}_0 &amp; \quad\equiv\quad \boldsymbol{\sigma} \quad \text{except:} \\
\boldsymbol{\sigma}_0[S(T)]_{\mathrm{b}} &amp; \quad\equiv\quad \boldsymbol{\sigma}[S(T)]_{\mathrm{b}} - T_{\mathrm{g}} T_{\mathrm{p}} \\
\boldsymbol{\sigma}_0[S(T)]_{\mathrm{n}} &amp; \quad\equiv\quad \boldsymbol{\sigma}[S(T)]_{\mathrm{n}} + 1
\end{aligned}</code></pre>
<p>检查点状态 <code class="katex-inline">\boldsymbol{\sigma}_0</code> 预扣了 <code class="katex-inline">T_{\mathrm{g}} T_{\mathrm{p}}</code></p>
<p>系统会在交易执行成功后，将剩余 gas 返还发送者</p>
<p>--</p>
<p>注意 XXX</p>
<p>公式 (61) 计算检查点状态 balance 时，不是扣除 <code class="katex-inline">v_0</code>，而是只扣了 <code class="katex-inline">T_{\mathrm{g}} T_{\mathrm{p}}</code>，未扣除 <code class="katex-inline">T_{\mathrm{v}}</code></p>
<p>实际上，<code class="katex-inline">v</code> 的处理，是在公式 (63) 执行交易时，在执行具体函数代码前，会从发送者 <code>Transfer</code> 到接收者</p>
<p>gas 与 value 分开处理，理解如下</p>
<ul>
<li>gas 本身与 value 语义本质量不同，不能混为一起</li>
<li>转账 value 的双方 balance 的改变应该是原子的，不应拆成前后两个上下文</li>
<li>gas 一定是外部账户支付的，而 value 转账的扣款账户可以是外部账户，也可以是智能合约</li>
</ul>
<pre><code class="language-go">// github.com/ethereum/go-ethereum@v1.10.6/core/vm/evm.go

// Call executes the contract associated with the addr with the given input as
// parameters. It also handles any necessary value transfer required and takes
// the necessary steps to create accounts and reverses the state in case of an
// execution error or failed value transfer.
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
    evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value)
    //...
}</code></pre>
<p>公式 (63) 执行交易后临时状态 <code class="katex-inline">\boldsymbol{\sigma}_{\mathrm{P}}</code></p>
<pre><code class="language-katex">(\boldsymbol{\sigma}_{\mathrm{P}}, g&#039;, A, z) \equiv \begin{cases}
\Lambda_{4}(\boldsymbol{\sigma}_0, S(T), T_{\mathrm{o}}, g, T_{\mathrm{p}}, T_{\mathrm{v}}, T_{\mathbf{i}}, 0, \varnothing, \top) &amp; \text{if} \quad T_{\mathrm{t}} = \varnothing \\
\Theta_{4}(\boldsymbol{\sigma}_0, S(T), T_{\mathrm{o}}, T_{\mathrm{t}}, T_{\mathrm{t}}, g, T_{\mathrm{p}}, T_{\mathrm{v}}, T_{\mathrm{v}}, T_{\mathbf{d}}, 0, \top) &amp; \text{otherwise}
\end{cases}</code></pre>
<p>状态转换</p>
<ul>
<li><code class="katex-inline">\boldsymbol{\sigma}_0</code> 检查点状态</li>
<li><code class="katex-inline">\boldsymbol{\sigma}_{\mathrm{P}}</code> 执行交易后临时状态<br />
操作</li>
<li>转换时需要区分交易类型
<ul>
<li><code class="katex-inline">T_{\mathrm{t}} = \varnothing</code> 未设置目标地址，说明是合约创建类型</li>
<li>否则，是消息调用类型</li>
</ul>
</li>
</ul>
<p>其中</p>
<ul>
<li><code class="katex-inline">\boldsymbol{\sigma}_{\mathrm{P}}</code>
<ul>
<li>交易执行后的临时状态</li>
</ul>
</li>
<li><code class="katex-inline">g'</code>
<ul>
<li>剩余 gas</li>
</ul>
</li>
<li><code class="katex-inline">A</code>
<ul>
<li>子状态</li>
</ul>
</li>
<li><code class="katex-inline">z</code>
<ul>
<li>状态码</li>
</ul>
</li>
<li><code class="katex-inline">T_{\mathrm{o}}</code>
<ul>
<li>original transactor</li>
<li>如果是 inner-transaction，则这里是智能合约的地址，而非 sender</li>
</ul>
</li>
<li><code class="katex-inline">\Lambda_{4}</code>
<ul>
<li>合约创建</li>
<li>参考 <a href="#7-contract-creation-合约创建">合约创建</a></li>
</ul>
</li>
<li><code class="katex-inline">\Theta_{4}</code>
<ul>
<li>消息调用</li>
<li>参考 <a href="#8-message-call-消息调用-internal-transaction">消息调用</a></li>
</ul>
</li>
<li><code class="katex-inline">\top</code>
<ul>
<li>数学符号，表示 bool 值</li>
<li><code class="katex-inline">\Lambda_{4}</code> 和 <code class="katex-inline">\Theta_{4}</code> 的末尾参数
<ul>
<li>含义：是否有权修改状态</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>--</p>
<p>关于 <code class="katex-inline">\top</code></p>
<table>
<thead>
<tr>
<th style="text-align: left;">OPCODE</th>
<th style="text-align: left;">函数</th>
<th style="text-align: left;">末尾参数值</th>
<th style="text-align: left;">含义</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;"><code>CREATE</code></td>
<td style="text-align: left;"><code class="katex-inline">\Lambda_{4}</code></td>
<td style="text-align: left;"><code class="katex-inline">I_{w}</code></td>
<td style="text-align: left;">保持调用时的读写权限</td>
</tr>
<tr>
<td style="text-align: left;"><code>CREATE2</code></td>
<td style="text-align: left;"><code class="katex-inline">\Lambda_{4}</code></td>
<td style="text-align: left;"><code class="katex-inline">I_{w}</code></td>
<td style="text-align: left;">保持调用时的读写权限</td>
</tr>
<tr>
<td style="text-align: left;"><code>STATICCALL</code></td>
<td style="text-align: left;"><code class="katex-inline">\Theta_{4}</code></td>
<td style="text-align: left;"><code class="katex-inline">\bot</code> (falsum)</td>
<td style="text-align: left;">只读</td>
</tr>
<tr>
<td style="text-align: left;"><code>CALL</code></td>
<td style="text-align: left;"><code class="katex-inline">\Theta_{4}</code></td>
<td style="text-align: left;"><code class="katex-inline">I_{w}</code></td>
<td style="text-align: left;">保持调用时的读写权限</td>
</tr>
<tr>
<td style="text-align: left;"><code>CALLCODE</code></td>
<td style="text-align: left;"><code class="katex-inline">\Theta_{4}</code></td>
<td style="text-align: left;"><code class="katex-inline">I_{w}</code></td>
<td style="text-align: left;">保持调用时的读写权限</td>
</tr>
<tr>
<td style="text-align: left;"><code>DELEGATECALL</code></td>
<td style="text-align: left;"><code class="katex-inline">\Theta_{4}</code></td>
<td style="text-align: left;"><code class="katex-inline">I_{w}</code></td>
<td style="text-align: left;">保持调用时的读写权限</td>
</tr>
</tbody>
</table>
<pre><code class="language-go">// github.com/ethereum/go-ethereum@v1.10.6/core/vm/interpreter.go
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
  // ...

  // Make sure the readOnly is only set if we aren&#039;t in readOnly yet.
  // This also makes sure that the readOnly flag isn&#039;t removed for child calls.
  if readOnly &amp;&amp; !in.readOnly {
    in.readOnly = true
    defer func() { in.readOnly = false }()
  }

  // ...

  for {
    // ...

    op = contract.GetOp(pc)
    operation := in.cfg.JumpTable[op]
    // If the operation is valid, enforce write restrictions
    if in.readOnly &amp;&amp; in.evm.chainRules.IsByzantium {
      // If the interpreter is operating in readonly mode, make sure no
      // state-modifying operation is performed. The 3rd stack item
      // for a call operation is the value. Transferring value from one
      // account to the others means the state is modified and should also
      // return with an error.
      if operation.writes || (op == CALL &amp;&amp; stack.Back(2).Sign() != 0) {
        return nil, ErrWriteProtection
      }
    }

    // ...
  }
  return nil, nil
}

//

// github.com/ethereum/go-ethereum@v1.10.6/core/vm/evm.go

// StaticCall executes the contract associated with the addr with the given input
// as parameters while disallowing any modifications to the state during the call.
// Opcodes that attempt to perform such modifications will result in exceptions
// instead of performing the modifications.
func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
    // ...
    ret, err = evm.interpreter.Run(contract, input, true)
    // ...
}

func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
    // ...
    ret, err = evm.interpreter.Run(contract, input, false)
    // ...
}

func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
    // ...
    ret, err = evm.interpreter.Run(contract, input, false)
    // ...
}

func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
    // ...
    ret, err = evm.interpreter.Run(contract, input, false)
    // ...
}

func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address) ([]byte, common.Address, uint64, error) {
    // ...
    ret, err = evm.interpreter.Run(contract, nil, false)
    // ...
}</code></pre>
<p>--</p>
<p>公式 (64) 执行时最多能消耗的 gas</p>
<pre><code class="language-katex">g \quad\equiv\quad T_{\mathrm{g}} - g_0</code></pre>
<p>公式 (65) 交易执行后 refund 计数器的变化</p>
<pre><code class="language-katex">A&#039;_{\mathrm{r}} \quad\equiv\quad A_{\mathrm{r}} + \sum_{i \in A_{\mathbf{s}}} R_{\mathrm{selfdestruct}}</code></pre>
<p>公式 (66) 交易执行后剩余的 gas</p>
<pre><code class="language-katex">g^* \quad\equiv\quad g&#039; + \min \left\{ \Big\lfloor \dfrac{T_{\mathrm{g}} - g&#039;}{2} \Big\rfloor, {A&#039;_{\mathrm{r}}} \right\}</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">g'</code>
<ul>
<li>交易执行后最终剩余的 gas</li>
</ul>
</li>
<li><code class="katex-inline">T_{\mathrm{g}} - g'</code>
<ul>
<li>交易实际消耗的 gas</li>
</ul>
</li>
<li><code class="katex-inline">g^*</code>
<ul>
<li>交易执行后最终需要返还的 gas</li>
</ul>
</li>
</ul>
<p>公式 (67) (68) (69) (70) 预备最终状态 <code class="katex-inline">\boldsymbol{\sigma}^*</code></p>
<pre><code class="language-katex">\begin{aligned}
\boldsymbol{\sigma}^* &amp; \quad\equiv\quad \boldsymbol{\sigma}_{\mathrm{P}} \quad \text{except} \\
\boldsymbol{\sigma}^*[S(T)]_{\mathrm{b}} &amp; \quad\equiv\quad \boldsymbol{\sigma}_{\mathrm{P}}[S(T)]_{\mathrm{b}} + g^* T_{\mathrm{p}} \\
\boldsymbol{\sigma}^*[m]_{\mathrm{b}} &amp; \quad\equiv\quad \boldsymbol{\sigma}_{\mathrm{P}}[m]_{\mathrm{b}} + (T_{\mathrm{g}} - g^*) T_{\mathrm{p}} \\
m &amp; \quad\equiv\quad {B_{\mathrm{H}}}_{\mathrm{c}}
\end{aligned}</code></pre>
<p>状态转换</p>
<ul>
<li><code class="katex-inline">\boldsymbol{\sigma}_{\mathrm{P}}</code> 执行交易后临时状态</li>
<li><code class="katex-inline">\boldsymbol{\sigma}^*</code> 预备最终状态<br />
操作</li>
<li>剩余的 gas 返回给发送者</li>
<li>消耗的 gas 发给 <code class="katex-inline">{B_{\mathrm{H}}}_{\mathrm{c}}</code> (beneficiary)</li>
</ul>
<p>公式 (71) (72) (73) 最终状态 <code class="katex-inline">\boldsymbol{\sigma}'</code></p>
<pre><code class="language-katex">\begin{aligned}
\boldsymbol{\sigma}&#039; &amp; \quad\equiv\quad \boldsymbol{\sigma}^* \quad \text{except} \\
\forall i \in A_{\mathbf{s}}: \boldsymbol{\sigma}&#039;[i] &amp; \quad=\quad \varnothing \\
\forall i \in A_{\mathbf{t}}: \boldsymbol{\sigma}&#039;[i] &amp; \quad=\quad \varnothing \quad\text{if}\quad \mathtt{DEAD}(\boldsymbol{\sigma}^*\kern -2pt, i)
\end{aligned}</code></pre>
<p>状态转换</p>
<ul>
<li><code class="katex-inline">\boldsymbol{\sigma}^*</code> 预备最终状态</li>
<li><code class="katex-inline">\boldsymbol{\sigma}'</code> 最终状态<br />
操作</li>
<li>删除自毁集合账户</li>
<li>删除接触账户集合中的空账户</li>
</ul>
<p>公式 (74) (75) (76)</p>
<pre><code class="language-katex">\begin{aligned}
\Upsilon^{\mathrm{g}}(\boldsymbol{\sigma}, T) &amp; \quad\equiv\quad T_{\mathrm{g}} - g^* \\
\Upsilon^{\mathbf{l}}(\boldsymbol{\sigma}, T) &amp; \quad\equiv\quad {A_{\mathbf{l}}} \\
\Upsilon^{\mathrm{z}}(\boldsymbol{\sigma}, T) &amp; \quad\equiv\quad z
\end{aligned}</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">\Upsilon^{\mathrm{g}}</code>
<ul>
<li>交易一共消耗的 gas</li>
</ul>
</li>
<li><code class="katex-inline">\Upsilon^\mathbf{l}</code>
<ul>
<li>交易生成的一系列日志</li>
</ul>
</li>
<li><code class="katex-inline">\Upsilon^{\mathrm{z}}</code>
<ul>
<li>交易执行返回的状态码</li>
</ul>
</li>
</ul>
<h2>7. Contract Creation 合约创建</h2>
<p>两个步骤</p>
<ul>
<li>合约创建</li>
<li>合约初始化
<ul>
<li>调用 <code class="katex-inline">\mathbf{i}</code></li>
</ul>
</li>
</ul>
<p>公式 (77) 盐</p>
<pre><code class="language-katex">\zeta \in \mathbb{B}_{32} \cup \mathbb{B}_{0}</code></pre>
<p>如果是通过 <code class="katex-inline">\small{CREATE2}</code> 创建合约，则 <code class="katex-inline">\zeta \neq \varnothing</code></p>
<p>公式 (78) 合约创建函数 <code class="katex-inline">\Lambda</code></p>
<pre><code class="language-katex">(\boldsymbol{\sigma}&#039;, g&#039;, A, z, \mathbf{o}) \equiv \Lambda(\boldsymbol{\sigma}, s, o, g, p, v, \mathbf{i}, e, \zeta, w)</code></pre>
<p>函数参数</p>
<ul>
<li><code class="katex-inline">\boldsymbol{\sigma}</code>
<ul>
<li>世界状态</li>
</ul>
</li>
<li><code class="katex-inline">s</code>
<ul>
<li>发送者 sender</li>
</ul>
</li>
<li><code class="katex-inline">o</code>
<ul>
<li>调用者 original transactor</li>
</ul>
</li>
<li><code class="katex-inline">g</code>
<ul>
<li>available gas</li>
</ul>
</li>
<li><code class="katex-inline">p</code>
<ul>
<li>gas price</li>
</ul>
</li>
<li><code class="katex-inline">v</code>
<ul>
<li>捐献: endowment</li>
</ul>
</li>
<li><code class="katex-inline">\mathbf{i}</code>
<ul>
<li>用于初始化合约的 EVM 代码(二进制字符串)</li>
</ul>
</li>
<li><code class="katex-inline">e</code>
<ul>
<li>当前消息调用/合约创建堆栈的深度</li>
</ul>
</li>
<li><code class="katex-inline">\zeta</code>
<ul>
<li>用于计算新合约地址的盐</li>
<li>可能不存在: <code class="katex-inline">\zeta = \varnothing</code></li>
</ul>
</li>
<li><code class="katex-inline">w</code>
<ul>
<li>是否有权改变状态</li>
<li>参考公式 (63)</li>
</ul>
</li>
</ul>
<p>函数输出</p>
<ul>
<li><code class="katex-inline">\boldsymbol{\sigma}'</code>
<ul>
<li>世界状态'</li>
</ul>
</li>
<li><code class="katex-inline">g'</code>
<ul>
<li>剩余 gas</li>
</ul>
</li>
<li><code class="katex-inline">A</code>
<ul>
<li>子状态</li>
<li>参考 <a href="#61-substrate-子状态">#6.1 Substrate</a></li>
</ul>
</li>
<li><code class="katex-inline">\mathbf{o}</code>
<ul>
<li>执行结果
<ul>
<li>对于合约创建而言，成功的话这里得到的是合约的 body code</li>
</ul>
</li>
<li>参考公式 (145)</li>
</ul>
</li>
</ul>
<p>公式 (79) (80) (81) 新创建合约的地址 <code class="katex-inline">a</code></p>
<pre><code class="language-katex">\begin{aligned}
a &amp; \equiv \mathtt{ADDR}(s, \boldsymbol{\sigma}[s]_{\mathrm{n}} - 1, \zeta, \mathbf{i}) \\
\mathtt{ADDR}(s, n, \zeta, \mathbf{i}) &amp; \equiv \mathcal{B}_{96..255}\Big(\mathtt{KEC}\big( L_{\mathrm{A}}(s, n, \zeta, \mathbf{i})\big) \Big) \\
L_{\mathrm{A}}(s, n, \zeta, \mathbf{i}) &amp; \equiv \begin{cases}
\mathtt{RLP}\big(\;(s, n)\;\big) &amp; \text{if}\ \zeta = \varnothing \\
(255) \cdot s \cdot \zeta \cdot \mathtt{KEC}(\mathbf{i}) &amp; \text{otherwise}
\end{cases}
\end{aligned}</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">L_{\mathrm{A}}</code>
<ul>
<li>判断合约创建方式是否为 <code class="katex-inline">\small{CREATE2}</code></li>
</ul>
</li>
<li><code class="katex-inline">\cdot</code>
<ul>
<li>拼接字节数组</li>
</ul>
</li>
<li><code class="katex-inline">\mathcal{B}_{a..b}(X)</code>
<ul>
<li>取二进制字节数组的第 <code class="katex-inline">[a, b]</code> 位</li>
</ul>
</li>
<li><code class="katex-inline">\boldsymbol{\sigma}[x]</code>
<ul>
<li>地址 <code class="katex-inline">x</code> 的世界状态</li>
<li>不存在，则为 <code class="katex-inline">\varnothing</code></li>
</ul>
</li>
</ul>
<p>注意</p>
<ul>
<li>公式(79) 中，传给合约地址计算函数 <code class="katex-inline">\Lambda</code> 的参数 nonce，值是 <code class="katex-inline">\boldsymbol{\sigma}[s]_{\mathrm{n}} - 1</code></li>
<li>理解: 在调用合约创建之前，系统已经把发送者的 nonce 加一</li>
</ul>
<p>公式 (82) (83) (84) (85) 合约创建后的世界状态 <code class="katex-inline">\boldsymbol{\sigma}^*</code></p>
<pre><code class="language-katex">\begin{aligned}
\boldsymbol{\sigma}^* &amp; \quad\equiv\quad \boldsymbol{\sigma} \quad \text{except:} \\
\boldsymbol{\sigma}^*[a] &amp; \quad=\quad \big( 1, v + v&#039;, \mathtt{TRIE}(\varnothing), \mathtt{KEC}\big(()\big) \big) \\
\boldsymbol{\sigma}^*[s] &amp; \quad=\quad \begin{cases}
\varnothing &amp; \text{if}\ \boldsymbol{\sigma}[s] = \varnothing \ \wedge\ v = 0 \\
\mathbf{a}^* &amp; \text{otherwise}
\end{cases} \\
\mathbf{a}^* &amp; \quad\equiv\quad (\boldsymbol{\sigma}[s]_{\mathrm{n}}, \boldsymbol{\sigma}[s]_{\mathrm{b}} - v, \boldsymbol{\sigma}[s]_{\mathbf{s}}, \boldsymbol{\sigma}[s]_{\mathrm{c}})
\end{aligned}</code></pre>
<p>公式 (86) <code class="katex-inline">v'</code> 合约地址在交易前就有的余额</p>
<pre><code class="language-katex">v&#039; \equiv \begin{cases}
0 &amp; \text{if} \quad \boldsymbol{\sigma}[a] = \varnothing\\
\boldsymbol{\sigma}[a]_{\mathrm{b}} &amp; \text{otherwise}
\end{cases}</code></pre>
<p>到这里，新创建合约的地址的状态已经设置好了(公式(83))</p>
<p>公式 (87) 调用 <code class="katex-inline">\Xi</code> 函数执行代码 <code class="katex-inline">\mathbf{i}</code> 来初始化合约</p>
<pre><code class="language-katex">(\boldsymbol{\sigma}^{**}, g^{**}, A, \mathbf{o}) \quad\equiv\quad \Xi(\boldsymbol{\sigma}^*, g, I, \{s, a\}) \\</code></pre>
<p><code class="katex-inline">\Xi</code> 函数的输出</p>
<ul>
<li><code class="katex-inline">\boldsymbol{\sigma}^{**}</code>
<ul>
<li>合约初始化后的世界状态</li>
</ul>
</li>
<li><code class="katex-inline">g^{**}</code>
<ul>
<li>剩余的可用 gas</li>
</ul>
</li>
<li><code class="katex-inline">A</code>
<ul>
<li>累积的子状态</li>
</ul>
</li>
<li><code class="katex-inline">\mathbf{o}</code>
<ul>
<li>新创建合约的 body code</li>
<li>由合约初始化 <code class="katex-inline">\mathbf{i}</code> 代码得到</li>
</ul>
</li>
</ul>
<p>公式 (88) (89) (90) (91) (92) (93) (94) (95) (96) 合约初始化后的世界状态 <code class="katex-inline">\boldsymbol{\sigma}^*</code></p>
<p><code class="katex-inline">I</code> 作为 <code class="katex-inline">\Xi</code> 函数的输入参数</p>
<ul>
<li>表示 执行环境的一系列变量</li>
<li>参考 <a href="#93-execution-environment-执行环境">#9.3 Execution Environment</a></li>
</ul>
<pre><code class="language-katex">\begin{aligned}
I_{\mathrm{a}} &amp; \quad\equiv\quad a \\
I_{\mathrm{o}} &amp; \quad\equiv\quad o \\
I_{\mathrm{p}} &amp; \quad\equiv\quad p \\
I_{\mathbf{d}} &amp; \quad\equiv\quad () \\
I_{\mathrm{s}} &amp; \quad\equiv\quad s \\
I_{\mathrm{v}} &amp; \quad\equiv\quad v \\
I_{\mathbf{b}} &amp; \quad\equiv\quad \mathbf{i} \\
I_{\mathrm{e}} &amp; \quad\equiv\quad e \\
I_{\mathrm{w}} &amp; \quad\equiv\quad w
\end{aligned}</code></pre>
<ul>
<li><code class="katex-inline">I_{\mathrm{a}}</code>
<ul>
<li>新创建合约的地址</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathrm{o}}</code>
<ul>
<li>调用者 original transactor</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathrm{p}}</code>
<ul>
<li>gas Price</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathbf{d}}</code>
<ul>
<li>input data</li>
<li>在这里为空，因为对于这个初始化调用而言没有 input</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathrm{s}}</code>
<ul>
<li>发送者 sender</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathrm{v}}</code>
<ul>
<li>捐献: endowment</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathbf{b}}</code>
<ul>
<li>用于初始化合约的 EVM 代码(二进制字符串)</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathrm{e}}</code>
<ul>
<li>当前消息调用/合约创建堆栈的深度</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathrm{w}}</code>
<ul>
<li>是否有权修改状态</li>
<li>参考公式 (63)</li>
</ul>
</li>
</ul>
<p>公式 (97) 合约初始化需要支付的 gas</p>
<pre><code class="language-katex">c \quad\equiv\quad G_{\mathrm{codedeposit}} \times \lVert \mathbf{o} \rVert</code></pre>
<ul>
<li>出现异常
<ul>
<li>初始化代码执行过程的异常
<ul>
<li>例子
<ul>
<li>因可用 gas <code class="katex-inline">g^{**}</code> 不够而导致 Out-Of-Gas 异常</li>
</ul>
</li>
</ul>
</li>
<li>成功初始化后可用 gas 不够支付 <code class="katex-inline">c</code>
<ul>
<li><code class="katex-inline">g^{**} < c</code></li>
<li>也会导致 Out-of-Gas</li>
</ul>
</li>
<li>其他场景
<ul>
<li>例子 TODO</li>
</ul>
</li>
</ul>
</li>
<li>导致
<ul>
<li>最终剩余 gas <code class="katex-inline">g‘</code> 为0</li>
<li>使初始化提前终止</li>
<li>合约初始化后的世界状态 <code class="katex-inline">\boldsymbol{\sigma}^{**}</code> 为空，即 <code class="katex-inline">\varnothing</code></li>
<li>最终状态与合约创建前一致</li>
</ul>
</li>
</ul>
<p>公式 (98) (99) (100) (101) 最终状态 <code class="katex-inline">\boldsymbol{\sigma}'</code></p>
<pre><code class="language-katex">\begin{aligned}
\quad g&#039; \quad\equiv\quad &amp; \begin{cases}
0 &amp; \text{if} \quad F \\
g^{**} - c &amp; \text{otherwise} \\
\end{cases} \\
\quad \boldsymbol{\sigma}&#039; \quad\equiv\quad &amp; \begin{cases}
\boldsymbol{\sigma} &amp; \text{if} \quad F \ \lor\ \boldsymbol{\sigma}^{**} = \varnothing \\
\boldsymbol{\sigma}^{**} \quad \text{except:} &amp; \\
\quad\boldsymbol{\sigma}&#039;[a] = \varnothing &amp; \text{if} \quad \mathtt{DEAD}(\boldsymbol{\sigma}^{**}, a) \\
\boldsymbol{\sigma}^{**} \quad \text{except:} &amp; \\
\quad\boldsymbol{\sigma}&#039;[a]_{\mathrm{c}} = \texttt{KEC}(\mathbf{o}) &amp; \text{otherwise}
\end{cases} \\
\quad z \quad\equiv\quad &amp; \begin{cases}
0 &amp; \text{if} \quad F \ \lor\ \boldsymbol{\sigma}^{**} = \varnothing \\
1 &amp; \text{otherwise}
\end{cases} \\
\text{where} \\
F \quad\equiv\quad  &amp; \big( \boldsymbol{\sigma}[a] \neq \varnothing \ \wedge\ \big(\boldsymbol{\sigma}[a]_c \neq \texttt{\small KEC}\big(()\big) \vee \boldsymbol{\sigma}[a]_n \neq 0 \big) \big) \quad \vee \\
&amp;(\boldsymbol{\sigma}^{**} = \varnothing \ \wedge\ \mathbf{o} = \varnothing) \quad \vee \\
&amp;g^{**} &lt; c \quad \vee \\
&amp;\lVert \mathbf{o} \rVert &gt; 24576
\end{aligned}</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">g'</code>
<ul>
<li>最终剩余的 gas，需要返还给最初交易发起者</li>
</ul>
</li>
<li><code class="katex-inline">\big( \boldsymbol{\sigma}[a] \neq \varnothing \ \wedge\ \big(\boldsymbol{\sigma}[a]_c \neq \texttt{\small KEC}\big(()\big) \vee \boldsymbol{\sigma}[a]_n \neq 0 \big) \big)</code>
<ul>
<li>目标地址已存在，且有 codeHash 或 nonce 不为0</li>
</ul>
</li>
<li><code class="katex-inline">\mathbf{o}</code>
<ul>
<li>新创建合约的 body code</li>
<li>由合约初始化 <code class="katex-inline">\mathbf{i}</code> 代码得到</li>
</ul>
</li>
<li><code class="katex-inline">\boldsymbol{\sigma}'[a]_{\mathrm{c}} = \texttt{KEC}(\mathbf{o})</code>
<ul>
<li>保存新建合约的 body code 的 hash 到 <code class="katex-inline">\boldsymbol{\sigma}'[a]_{\mathrm{c}}</code></li>
</ul>
</li>
<li>
<p><code class="katex-inline">\boldsymbol{\sigma}^{**} = \varnothing \ \wedge\ \mathbf{o} = \varnothing</code></p>
<ul>
<li>含义
<ul>
<li>执行 <code class="katex-inline">\mathbf{i}</code> 后得到的字节码，即合约代码 <code class="katex-inline">\mathbf{o}</code> 为空</li>
</ul>
</li>
<li>
<p>失败例子 Ropsten</p>
<ul>
<li><code class="katex-inline">\text{\small SELFDESTRUCT}</code>
<ul>
<li>结论
<ul>
<li>根据执行结果，剩余 gas 有返还，所以不属于 <code class="katex-inline">F</code></li>
</ul>
</li>
<li>代码</li>
</ul>
</li>
</ul>
<pre><code class="language-js">pragma solidity >=0.7.0 <0.9.0;
contract Storage {
  constructor(address user) payable {
      address payable u = payable(address(user));
      selfdestruct(u);
  }
}</code></pre>
<ul>
<li>交易
<ul>
<li>0xf06eb45dd26ec1f5a7b6e876ddf2253b0716bf50b17185f8a796d6b247d7e5d1</li>
<li>执行细节
<ul>
<li>From 0xcf60d818200f23499ef4c88437e83da7a6d85ac7</li>
<li>To [Contract 0x8CAE6369489542352335f97465Ba95EBB1a2fCaF Created]
<ul>
<li>TRANSFER  0.01 Ether From 0x8CAE6369489542352335f97465Ba95EBB1a2fCaF To  0x6352e8a946050224b6fd9575497391af76f74a89</li>
<li>SELF DESTRUCT Contract 0x8CAE6369489542352335f97465Ba95EBB1a2fCaF</li>
</ul>
</li>
<li>Value 0.01 Ether ($0.00)</li>
<li>Gas Limit 300,000</li>
<li>Gas Used by Transaction 64,402 (21.47%)</li>
</ul>
</li>
</ul>
</li>
<li>合约
<ul>
<li>0x8CAE6369489542352335f97465Ba95EBB1a2fCaF
<ul>
<li>balance: 0</li>
<li>nonce: 0</li>
<li>code: 0x</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>结论</p>
<ul>
<li>
<p>要么带着初始捐款(endowment)成功创建合约</p>
</li>
<li>
<p>要么不会创建任何合约且不会进行转账</p>
</li>
<li>
<p>REVERT</p>
<ul>
<li>参考
<ul>
<li><a href="https://info.etherscan.com/reason-for-failed-transaction/">What are the Reasons for Failed Transactions?</a></li>
<li><a href="https://medium.com/blockchannel/the-use-of-revert-assert-and-require-in-solidity-and-the-new-revert-opcode-in-the-evm-1a3a7990e06e">Solidity Learning: Revert(), Assert(), and Require() in Solidity, and the New REVERT Opcode in the EVM</a></li>
</ul>
</li>
<li>注释</li>
</ul>
<pre><code class="language-go">// github.com/ethereum/go-ethereum@v1.10.6/core/vm/interpreter.go

// It's important to note that any errors returned by the interpreter should be
// considered a revert-and-consume-all-gas operation except for
// ErrExecutionReverted which means revert-and-keep-gas-left.
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
  // ...
}</code></pre>
<ul>
<li>
<p>测试</p>
<ul>
<li>结论
<ul>
<li>不满足 <code class="katex-inline">F</code>，因此 <code class="katex-inline">g' = g^{**} - c</code>
<ul>
<li>剩余 gas 和 value 会被归还</li>
</ul>
</li>
<li>满足 <code class="katex-inline">\mathtt{DEAD}(\boldsymbol{\sigma}^{**}, a)</code>，因此 <code class="katex-inline">\boldsymbol{\sigma}'[a] = \varnothing</code>
<ul>
<li>其余状态不变</li>
</ul>
</li>
</ul>
</li>
<li>证明
<ul>
<li>代码 (assert 和 require 都测试过，都返还了 gas，跟参考文章结论不一样)</li>
</ul>
</li>
</ul>
<pre><code class="language-js">pragma solidity >=0.7.0 <0.9.0;
contract Storage {
  uint256 number;
  constructor(address user, uint256 num) payable {
      require (msg.sender == user);
      number = num;
  }
}</code></pre>
<ul>
<li>部署
<ul>
<li>发起部署的地址与构造函数的地址不同，使 require/assert 失败</li>
</ul>
</li>
<li>
<p>例子 Ropsten</p>
<ul>
<li>成功创建了合约，但初始化时 REVERTED
<ul>
<li>交易
<ul>
<li>require
<ul>
<li>0xb407a7fa764ac8ddcd556c5185357b328af8b7070babd282a49af606ea1d98ad</li>
<li>执行细节
<ul>
<li>From  0xcf60d818200f23499ef4c88437e83da7a6d85ac7</li>
<li>To [Contract 0xdc1646faae75131d84b0d1213bb2bb6927d2eba5 Created]
<ul>
<li>Warning! Error encountered during contract execution [Reverted]</li>
</ul>
</li>
<li>Value  0.01 Ether ($0.00) - [CANCELLED]</li>
<li>Gas Limit 3,000,000</li>
</ul>
</li>
<li>合约
<ul>
<li>0xDC1646fAAe75131D84B0d1213Bb2BB6927D2eba5
<ul>
<li>balance: 0</li>
<li>nonce: 0</li>
<li>code: 0x</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>assert
<ul>
<li>0x453066a33244bcf4b130e741a68032f14b536013505e0712cb99e68963e948a5</li>
<li>合约
<ul>
<li>0xE34B1dB1cbBe6ea36952076b1578995Fe85A7061
<ul>
<li>balance: 0</li>
<li>nonce: 0</li>
<li>code: 0x</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>根据公式(14)，新创建的合约是个 EMPTY 账户</li>
</ul>
</li>
</ul>
<pre><code class="language-js">    const targetContract = '0xbd83EF1a5A45b54d94895C1897aF2d00154520D5';

    const balance = await web3.eth.getBalance(targetContract);
    const nonce = await web3.eth.getTransactionCount(targetContract);
    const code = await web3.eth.getCode(targetContract);

    console.log(<code class="kb-btn">balance: ${balance}</code>);
    console.log(<code class="kb-btn">nonce: ${nonce}</code>);
    console.log(<code class="kb-btn">code: ${code}</code>);

    // storageRoot
    //  无法从 web3 直接获得
    //  参考 https://github.com/medvedev1088/ethereum-merkle-patricia-trie-example</code></pre>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2>8. Message Call 消息调用 (internal Transaction)</h2>
<p>公式 (102) 消息调用函数 <code class="katex-inline">\Theta</code></p>
<pre><code class="language-katex">(\boldsymbol{\sigma}&#039;, g&#039;, A, z, \mathbf{o}) \equiv {\Theta}(\boldsymbol{\sigma}, s, o, r, c, g, p, v, \tilde{v}, \mathbf{d}, e, w)</code></pre>
<p>函数参数</p>
<ul>
<li><code class="katex-inline">\boldsymbol{\sigma}</code>
<ul>
<li>世界状态</li>
</ul>
</li>
<li><code class="katex-inline">s</code>
<ul>
<li>发送者 sender</li>
</ul>
</li>
<li><code class="katex-inline">o</code>
<ul>
<li>调用者 original transactor</li>
</ul>
</li>
<li><code class="katex-inline">r</code>
<ul>
<li>接收者 recipient</li>
</ul>
</li>
<li><code class="katex-inline">c</code>
<ul>
<li>消息调用执行在哪个地址上的代码，通常等于 <code class="katex-inline">r</code></li>
<li>参考 <a href="#h2-instruction-set">Appendix H2. Instruction Set</a> 的 <code class="katex-inline">{\small CALL}</code>, <code class="katex-inline">{\small CALLCODE}</code></li>
</ul>
</li>
<li><code class="katex-inline">g</code>
<ul>
<li>available gas</li>
</ul>
</li>
<li><code class="katex-inline">p</code>
<ul>
<li>gas price</li>
</ul>
</li>
<li><code class="katex-inline">v</code>
<ul>
<li>捐献: endowment</li>
</ul>
</li>
<li><code class="katex-inline">\mathbf{d}</code>
<ul>
<li>消息调用的 input data (二进制字符串)</li>
</ul>
</li>
<li><code class="katex-inline">e</code>
<ul>
<li>当前消息调用/合约创建堆栈的深度</li>
</ul>
</li>
<li><code class="katex-inline">w</code>
<ul>
<li>是否有权改变状态</li>
<li>参考公式 (63)</li>
</ul>
</li>
</ul>
<p>函数输出</p>
<ul>
<li><code class="katex-inline">\boldsymbol{\sigma}'</code>
<ul>
<li>世界状态'</li>
</ul>
</li>
<li><code class="katex-inline">g'</code>
<ul>
<li>剩余 gas</li>
</ul>
</li>
<li><code class="katex-inline">A</code>
<ul>
<li>子状态</li>
<li>参考 <a href="#61-substrate-子状态">#6.1 Substrate</a></li>
</ul>
</li>
<li><code class="katex-inline">z</code>
<ul>
<li>status code</li>
</ul>
</li>
<li><code class="katex-inline">\mathbf{o}</code>
<ul>
<li>output data</li>
<li>参考公式 (145)</li>
</ul>
</li>
</ul>
<p>注意区分</p>
<ul>
<li><code class="katex-inline">v</code>
<ul>
<li>msg.value</li>
</ul>
</li>
<li><code class="katex-inline">\tilde{v}</code>
<ul>
<li><code class="katex-inline">{\small DELEGATECALL}</code> 指令上下文的中的 value</li>
</ul>
</li>
</ul>
<p>公式 (103) 临时状态 <code class="katex-inline">\boldsymbol{\sigma}_1</code></p>
<pre><code class="language-katex">\begin{aligned}
&amp;\boldsymbol{\sigma}_1[r]_{\mathrm{b}} \equiv \boldsymbol{\sigma}[r]_{\mathrm{b}} + v \quad\wedge\quad \boldsymbol{\sigma}_1[s]_{\mathrm{b}} \equiv \boldsymbol{\sigma}[s]_{\mathrm{b}} - v \\
&amp; unless s = r
\end{aligned}</code></pre>
<p>意味着</p>
<p>公式 (104) (105) (106) (107) (108) (109) 状态转换 <code class="katex-inline">\boldsymbol{\sigma}</code> -&gt; <code class="katex-inline">\boldsymbol{\sigma}_1'</code> -&gt; <code class="katex-inline">\boldsymbol{\sigma}_1</code></p>
<pre><code class="language-katex">%\boxed{%
\boldsymbol{\sigma}_1 \equiv \boldsymbol{\sigma}_1&#039; \quad \text{except:}
%}%</code></pre>
<pre><code class="language-katex">\boldsymbol{\sigma}_1[s] \equiv \begin{cases}
\varnothing &amp; \text{if}\ \boldsymbol{\sigma}_1&#039;[s] = \varnothing \ \wedge\ v = 0 \\
\mathbf{a}_1 &amp;\text{otherwise} \\
\end{cases} \\</code></pre>
<pre><code class="language-katex">\mathbf{a}_1 \equiv \left(\boldsymbol{\sigma}_1&#039;[s]_{\mathrm{n}}, \boldsymbol{\sigma}_1&#039;[s]_{\mathrm{b}} - v, \boldsymbol{\sigma}_1&#039;[s]_{\mathbf{s}}, \boldsymbol{\sigma}_1&#039;[s]_{\mathrm{c}}\right)</code></pre>
<pre><code class="language-katex">\text{and}\quad \boldsymbol{\sigma}_1&#039; \equiv \boldsymbol{\sigma} \quad \text{except:}</code></pre>
<pre><code class="language-katex">\begin{cases}
\boldsymbol{\sigma}_1&#039;[r] \equiv (0, v, \mathtt{TRIE}(\varnothing), \mathtt{KEC}(())) &amp; \text{if} \quad \boldsymbol{\sigma}[r] = \varnothing \wedge v \neq 0 \\
\boldsymbol{\sigma}_1&#039;[r] \equiv \varnothing &amp; \text{if}\quad \boldsymbol{\sigma}[r] = \varnothing \wedge v = 0 \\
\boldsymbol{\sigma}_1&#039;[r] \equiv \mathbf{a}_1&#039; &amp; \text{otherwise}
\end{cases}</code></pre>
<pre><code class="language-katex">\mathbf{a}_1&#039; \equiv (\boldsymbol{\sigma}[r]_{\mathrm{n}}, \boldsymbol{\sigma}[r]_{\mathrm{b}} + v, \boldsymbol{\sigma}[r]_{\mathbf{s}}, \boldsymbol{\sigma}[r]_{\mathrm{c}})</code></pre>
<p>执行过程参考 <a href="#9-execution-model-执行模型">#9 Execution Model</a></p>
<p>公式 (110) (111) ... (123) 最终状态 <code class="katex-inline">\boldsymbol{\sigma}'</code></p>
<pre><code class="language-katex">\begin{aligned}
\boldsymbol{\sigma}&#039; &amp; \quad\equiv\quad \begin{cases}
\boldsymbol{\sigma} &amp; \text{if} \quad \boldsymbol{\sigma}^{**} = \varnothing \\
\boldsymbol{\sigma}^{**} &amp; \text{otherwise}
\end{cases} \\
g&#039; &amp; \quad\equiv\quad \begin{cases}
0 &amp; \text{if} \quad \boldsymbol{\sigma}^{**} = \varnothing \ \wedge \mathbf{o} = \varnothing \\
g^{**} &amp; \text{otherwise}
\end{cases} \\
z &amp; \quad\equiv\quad \begin{cases}
0 &amp; \text{if} \quad \boldsymbol{\sigma}^{**} = \varnothing \\
1 &amp; \text{otherwise}
\end{cases} \\
(\boldsymbol{\sigma}^{**}, g^{**},A, \mathbf{o}) &amp; \quad\equiv\quad \Xi\\
I_{\mathrm{a}} &amp; \quad\equiv\quad r \\
I_{\mathrm{o}} &amp; \quad\equiv\quad o \\
I_{\mathrm{p}} &amp; \quad\equiv\quad p \\
I_{\mathbf{d}} &amp; \quad\equiv\quad \mathbf{d} \\
I_{\mathrm{s}} &amp; \quad\equiv\quad s \\
I_{\mathrm{v}} &amp; \quad\equiv\quad \tilde{v} \\
I_{\mathrm{e}} &amp; \quad\equiv\quad e \\
I_{\mathrm{w}} &amp; \quad\equiv\quad w \\
\mathbf{t} &amp; \quad\equiv\quad \{s, r\} \\
where \\
\end{aligned} \\
\Xi \equiv \begin{cases}
\Xi_{\mathtt{ECREC}}(\boldsymbol{\sigma}_1, g, I, \mathbf{t}) &amp; \text{if} \quad c = 1 \\
\Xi_{\mathtt{SHA256}}(\boldsymbol{\sigma}_1, g, I, \mathbf{t}) &amp; \text{if} \quad c = 2 \\
\Xi_{\mathtt{RIP160}}(\boldsymbol{\sigma}_1, g, I, \mathbf{t}) &amp; \text{if} \quad c = 3 \\
\Xi_{\mathtt{ID}}(\boldsymbol{\sigma}_1, g, I, \mathbf{t}) &amp; \text{if} \quad c = 4 \\
\Xi_{\mathtt{EXPMOD}}(\boldsymbol{\sigma}_1, g, I, \mathbf{t}) &amp; \text{if} \quad c = 5 \\
\Xi_{\mathtt{BN\_ADD}}(\boldsymbol{\sigma}_1, g, I, \mathbf{t}) &amp; \text{if} \quad c = 6 \\
\Xi_{\mathtt{BN\_MUL}}(\boldsymbol{\sigma}_1, g, I, \mathbf{t}) &amp; \text{if} \quad c = 7 \\
\Xi_{\mathtt{SNARKV}}(\boldsymbol{\sigma}_1, g, I, \mathbf{t}) &amp; \text{if} \quad c = 8 \\
\Xi_{\mathtt{BLAKE2\_F}}(\boldsymbol{\sigma}_1, g, I, \mathbf{t}) &amp; \text{if} \quad c = 9 \\
\Xi(\boldsymbol{\sigma}_1, g, I, \mathbf{t}) &amp; \text{otherwise} \end{cases}</code></pre>
<pre><code class="language-katex">\text{Let} \; \mathtt{KEC}(I_{\mathbf{b}}) = \boldsymbol{\sigma}[c]_{\mathrm{c}}</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">I_{\mathrm{a}}</code>
<ul>
<li>接收者地址</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathrm{o}}</code>
<ul>
<li>调用者 original transactor</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathrm{p}}</code>
<ul>
<li>gas Price</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathbf{d}}</code>
<ul>
<li>消息调用的 input data (二进制字符串)</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathrm{s}}</code>
<ul>
<li>发送者 sender</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathrm{v}}</code>
<ul>
<li><code class="katex-inline">\tilde{v}</code></li>
<li><code class="katex-inline">{\small DELEGATECALL}</code> 指令上下文的中的 value</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathrm{e}}</code>
<ul>
<li>当前消息调用/合约创建堆栈的深度</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathrm{w}}</code>
<ul>
<li>是否有权改变状态</li>
<li>参考公式 (63)</li>
</ul>
</li>
</ul>
<p>注意</p>
<ul>
<li>映射
<ul>
<li>客户端会存储映射 <code class="katex-inline">\mathtt{KEC}(I_{\mathbf{b}})</code> =&gt; <code class="katex-inline">I_{\mathbf{b}}</code></li>
<li>这样可以方便根据 <code class="katex-inline">\boldsymbol{\sigma}[c]_{\mathrm{c}}</code> 索引并取出目标地址的代码 <code class="katex-inline">I_{\mathbf{b}}</code> 以执行</li>
</ul>
</li>
<li>预编译合约
<ul>
<li>c 在 [1, 9] 表示预编译合约</li>
<li>参考 <a href="#appendix-e-precompiled-contracts"># Appendix E. Precompiled Contracts</a></li>
</ul>
</li>
</ul>
<h2>9. Execution Model 执行模型</h2>
<p>参考 go-ethereum 源码</p>
<pre><code class="language-go">// github.com/ethereum/go-ethereum@v1.10.6/core/state_processor.go
func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error);

  // github.com/ethereum/go-ethereum@v1.10.6/core/state_transition.go
  func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) (*ExecutionResult, error);
    func (st *StateTransition) TransitionDb() (*ExecutionResult, error);

      // github.com/ethereum/go-ethereum@v1.10.6/core/vm/evm.go
      func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error);

        // github.com/ethereum/go-ethereum@v1.10.6/core/vm/interpreter.go
        func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error);

          // github.com/ethereum/go-ethereum@v1.10.6/core/vm/jump_table.go
          type (
            executionFunc func(pc *uint64, interpreter *EVMInterpreter, callContext *ScopeContext) ([]byte, error)
          )

          type operation struct {
            execute     executionFunc
          }

            // github.com/ethereum/go-ethereum@v1.10.6/core/vm/instructions.go</code></pre>
<h3>9.1 Basics 基础</h3>
<h3>9.2 Fees Overview 费用概述</h3>
<ul>
<li>消耗
<ul>
<li>代码执行</li>
<li>进一步的 消息调用 或 合约创建</li>
<li>内存使用</li>
</ul>
</li>
<li>refund
<ul>
<li>如果清除了一块存储，系统会免除这项操作的费用，且返还一定额度的 refund</li>
<li>参考 <a href="#appendix-g-fee-schedule"># Appendix G. Fee Schedule</a></li>
<li><code class="katex-inline">R_{\mathrm{sclear}}</code></li>
<li><code class="katex-inline">R_{\mathrm{selfdestruct}}</code></li>
</ul>
</li>
</ul>
<h3>9.3 Execution Environment 执行环境</h3>
<p><code class="katex-inline">I</code> 执行环境</p>
<p>组成</p>
<ul>
<li><code class="katex-inline">I_{\mathrm{a}}</code>
<ul>
<li>接收者地址</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathrm{o}}</code>
<ul>
<li>调用者 original transactor</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathrm{p}}</code>
<ul>
<li>gas Price</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathbf{d}}</code>
<ul>
<li>消息调用的 input data (二进制字符串)</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathrm{s}}</code>
<ul>
<li>发送者 sender</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathrm{v}}</code>
<ul>
<li>value</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathrm{b}}</code>
<ul>
<li>接收者的代码，将被执行</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathrm{H}}</code>
<ul>
<li>当前区块的区块头</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathrm{e}}</code>
<ul>
<li>当前消息调用/合约创建堆栈的深度</li>
</ul>
</li>
<li><code class="katex-inline">I_{\mathrm{w}}</code>
<ul>
<li>是否有权改变状态</li>
<li>参考公式 (63)</li>
</ul>
</li>
</ul>
<p>公式 (124) 函数 <code class="katex-inline">\Xi</code></p>
<pre><code class="language-katex">(\boldsymbol{\sigma}&#039;, g&#039;, A, \mathbf{o}) \equiv \Xi(\boldsymbol{\sigma}, g, I)</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">\mathbf{o}</code>
<ul>
<li>执行结果 output</li>
</ul>
</li>
</ul>
<p>公式 (125) 累计子状态 <code class="katex-inline">A</code></p>
<pre><code class="language-katex">A \equiv (A_{\mathbf{s}}, A_{\mathbf{l}}, A_{\mathbf{t}}, A_{\mathrm{r}})</code></pre>
<ul>
<li><code class="katex-inline">A_{\mathbf{s}}</code>
<ul>
<li>自毁集合</li>
<li>交易执行完成后会被销毁的账户</li>
</ul>
</li>
<li><code class="katex-inline">A_{\mathbf{l}}</code>
<ul>
<li>一系列日志</li>
<li>方便外界旁观者简单跟踪合约调用</li>
</ul>
</li>
<li><code class="katex-inline">A_{\mathbf{t}}</code>
<ul>
<li>交易接触的账户</li>
<li>其中的 EMPTY 账户会被删除</li>
</ul>
</li>
<li><code class="katex-inline">A_{\mathbf{r}}</code>
<ul>
<li>refund balance</li>
<li>累计需要归还的 gas</li>
</ul>
</li>
</ul>
<h3>9.4 Execution Overview 执行概述</h3>
<p>公式 (126) (127) (128) ... (137) (138) 函数 <code class="katex-inline">{\Xi}</code> 的定义</p>
<pre><code class="language-katex">\begin{aligned}
\Xi(\boldsymbol{\sigma}, g, I, T) &amp; \quad\equiv\quad (\boldsymbol{\sigma}&#039;\!, \boldsymbol{\mu}&#039;_{\mathrm{g}}, A, \mathbf{o}) \\
(\boldsymbol{\sigma}&#039;, \boldsymbol{\mu}&#039;\!, A, ..., \mathbf{o}) &amp; \quad\equiv\quad X\big((\boldsymbol{\sigma}, \boldsymbol{\mu}, A^0\!, I)\big) \\
\boldsymbol{\mu}_{\mathrm{g}} &amp; \quad\equiv\quad g \\
\boldsymbol{\mu}_{\mathrm{pc}} &amp; \quad\equiv\quad 0 \\
\boldsymbol{\mu}_{\mathbf{m}} &amp; \quad\equiv\quad (0, 0, ...) \\
\boldsymbol{\mu}_{\mathrm{i}} &amp; \quad\equiv\quad 0 \\
\boldsymbol{\mu}_{\mathbf{s}} &amp; \quad\equiv\quad () \\
\boldsymbol{\mu}_{\mathbf{o}} &amp; \quad\equiv\quad ()
\end{aligned}</code></pre>
<pre><code class="language-katex">X\big( (\boldsymbol{\sigma}, \boldsymbol{\mu}, A, I) \big) \equiv \begin{cases}
\big(\varnothing, \boldsymbol{\mu}, A^0, I, \varnothing\big) &amp; \text{if} \quad Z(\boldsymbol{\sigma}, \boldsymbol{\mu}, I) \\
\big(\varnothing, \boldsymbol{\mu}&#039;, A^0, I, \mathbf{o}\big) &amp; \text{if} \quad w = \text{\small REVERT} \\
O(\boldsymbol{\sigma}, \boldsymbol{\mu}, A, I) \cdot \mathbf{o} &amp; \text{if} \quad \mathbf{o} \neq \varnothing \\
X\big(O(\boldsymbol{\sigma}, \boldsymbol{\mu}, A, I)\big) &amp; \text{otherwise} \\
\end{cases}</code></pre>
<p>where</p>
<pre><code class="language-katex">\begin{aligned}
\mathbf{o} &amp; \quad\equiv\quad H(\boldsymbol{\mu}, I) \\
(a, b, c, d) \cdot e &amp; \quad\equiv\quad (a, b, c, d, e) \\
\boldsymbol{\mu}&#039; &amp; \quad\equiv\quad \boldsymbol{\mu}\ \text{except:} \\
\boldsymbol{\mu}&#039;_{\mathrm{g}} &amp; \quad\equiv\quad \boldsymbol{\mu}_{\mathrm{g}} - C(\boldsymbol{\sigma}, \boldsymbol{\mu}, I)
\end{aligned}</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">X</code>
<ul>
<li>循环调用直到遇到异常或执行完成</li>
</ul>
</li>
<li><code class="katex-inline">O</code>
<ul>
<li>单步执行当前指令 <code class="katex-inline">w</code></li>
<li>参考 (146)</li>
</ul>
</li>
<li><code class="katex-inline">Z</code>
<ul>
<li>检查是否异常</li>
<li>参考 (140)</li>
</ul>
</li>
<li><code class="katex-inline">o</code>
<ul>
<li><code class="katex-inline">H</code> 的返回结果</li>
</ul>
</li>
<li><code class="katex-inline">H</code>
<ul>
<li>检查是否正常终止</li>
<li>返回 <code class="katex-inline">\varnothing</code> 空，表示继续执行</li>
<li>返回 <code class="katex-inline">()</code> 空集，表示停止执行</li>
<li>否则，表示有意识的停止执行并附加执行结果 <code class="katex-inline">o</code></li>
<li>参考 (145)</li>
</ul>
</li>
<li><code class="katex-inline">C(\boldsymbol{\sigma}, \boldsymbol{\mu}, I)</code>
<ul>
<li>参考 (314)</li>
<li>Appendix H.1. Gas Cost</li>
</ul>
</li>
<li><code class="katex-inline">\boldsymbol{\mu}_{\mathrm{g}}</code>
<ul>
<li>剩余可用 gas</li>
</ul>
</li>
<li><code class="katex-inline">\boldsymbol{\mu}_{\mathrm{pc}}</code>
<ul>
<li>program counter</li>
</ul>
</li>
<li><code class="katex-inline">\boldsymbol{\mu}_{\mathbf{m}}</code>
<ul>
<li>memory contents</li>
</ul>
</li>
<li><code class="katex-inline">\boldsymbol{\mu}_{\mathrm{i}}</code>
<ul>
<li>active number of words in memory</li>
</ul>
</li>
<li><code class="katex-inline">\boldsymbol{\mu}_{\mathbf{s}}</code>
<ul>
<li>stack contents</li>
</ul>
</li>
<li><code class="katex-inline">\boldsymbol{\mu}_{\mathbf{o}}</code>
<ul>
<li>执行输出 output</li>
</ul>
</li>
</ul>
<p>注意</p>
<ul>
<li>公式 (146)，执行函数 <code class="katex-inline">\Xi</code> 后，会丢弃 <code class="katex-inline">I'</code> 并记录 <code class="katex-inline">\boldsymbol{\mu}'_{\mathrm{g}}</code> 和 <code class="katex-inline">o</code></li>
</ul>
<h4>9.4.1 Machine State</h4>
<p>机器状态 <code class="katex-inline">\boldsymbol{\mu}</code> 组成为 <code class="katex-inline">(g, pc, \mathbf{m}, i, \mathbf{s})</code></p>
<p>(139) 当前待执行的指令 <code class="katex-inline">w</code></p>
<pre><code class="language-katex">w \equiv \begin{cases} I_{\mathbf{b}}[\boldsymbol{\mu}_{\mathrm{pc}}] &amp; \text{if} \quad \boldsymbol{\mu}_{\mathrm{pc}} &lt; \lVert I_{\mathbf{b}} \rVert \\
\text{{\small STOP}} &amp; \text{otherwise}
\end{cases}</code></pre>
<pre><code class="language-js">// github.com/ethereum/go-ethereum@v1.10.6/core/vm/interpreter.go
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error)
  op = contract.GetOp(pc)
    operation := in.cfg.JumpTable[op]

// github.com/ethereum/go-ethereum@v1.10.6/core/vm/contract.go
// GetOp returns the n&#039;th element in the contract&#039;s byte array
func (c *Contract) GetOp(n uint64) OpCode {
    return OpCode(c.GetByte(n))
}

// GetByte returns the n&#039;th byte in the contract&#039;s byte array
func (c *Contract) GetByte(n uint64) byte {
    if n &lt; uint64(len(c.Code)) {
        return c.Code[n]
    }

    return 0 // STOP
}</code></pre>
<h4>9.4.2 Exception Halting</h4>
<p>公式 (140) (141) 异常检查函数 <code class="katex-inline">Z</code></p>
<pre><code class="language-katex">\begin{aligned}
Z(\boldsymbol{\sigma}, \boldsymbol{\mu}, I) \quad\equiv\quad
&amp; \boldsymbol{\mu}_g &lt; C(\boldsymbol{\sigma}, \boldsymbol{\mu}, I) \quad \vee \\
&amp; \mathbf{\delta}_w = \varnothing \quad \vee \\
&amp; \lVert\boldsymbol{\mu}_\mathbf{s}\rVert &lt; \mathbf{\delta}_w \quad \vee \\
&amp; ( w = \text{\small JUMP} \; \wedge \; \boldsymbol{\mu}_\mathbf{s}[0] \notin D(I_\mathbf{b}) ) \quad \vee \\
&amp; ( w = \text{\small JUMPI} \; \wedge \; \boldsymbol{\mu}_\mathbf{s}[1] \neq 0 \; \wedge\boldsymbol{\mu}_\mathbf{s}[0] \notin D(I_\mathbf{b}) ) \quad \vee \\
&amp; ( w = \text{\small RETURNDATACOPY} \; \wedge \boldsymbol{\mu}_{\mathbf{s}}[1] + \boldsymbol{\mu}_{\mathbf{s}}[2] &gt; \lVert\boldsymbol{\mu}_{\mathbf{o}}\rVert) \quad \vee \\
&amp; \lVert\boldsymbol{\mu}_\mathbf{s}\rVert - \mathbf{\delta}_w + \mathbf{\alpha}_w &gt; 1024 \quad \vee \\
&amp; ( \neg I_{\mathrm{w}} \; \wedge \; W(w, \boldsymbol{\mu}) ) \quad \vee \\
&amp; ( w = \text{\small SSTORE} \; \wedge \; \boldsymbol{\mu}_g \leqslant G_{\mathrm{callstipend}} )
\end{aligned}</code></pre>
<p>where</p>
<pre><code class="language-katex">\begin{aligned}
W(w, \boldsymbol{\mu}) \quad\equiv\quad
&amp; w \in \{\text{\small CREATE}, \text{\small CREATE2}, \text{\small SSTORE}, \text{\small SELFDESTRUCT}\} \ \vee \\
&amp; \text{\small LOG0} \le w \; \wedge \; w \le \text{\small LOG4} \quad \vee \\
&amp; w = \text{\small CALL} \; \wedge \; \boldsymbol{\mu}_{\mathbf{s}}[2] \neq 0
\end{aligned}</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">\delta</code>
<ul>
<li>指令 <code class="katex-inline">w</code> 的出栈个数</li>
</ul>
</li>
<li><code class="katex-inline">\alpha</code>
<ul>
<li>指令 <code class="katex-inline">w</code> 的入栈个数</li>
</ul>
</li>
<li>栈的索引
<ul>
<li>从上往下增长</li>
<li>栈顶为0</li>
</ul>
</li>
</ul>
<p>异常情况</p>
<pre><code class="language-go">// github.com/ethereum/go-ethereum@v1.10.6/core/vm/errors.go

// List evm execution errors
var (
  ErrOutOfGas                 = errors.New(&quot;out of gas&quot;)
  ErrCodeStoreOutOfGas        = errors.New(&quot;contract creation code storage out of gas&quot;)
  ErrDepth                    = errors.New(&quot;max call depth exceeded&quot;)
  ErrInsufficientBalance      = errors.New(&quot;insufficient balance for transfer&quot;)
  ErrContractAddressCollision = errors.New(&quot;contract address collision&quot;)
  ErrExecutionReverted        = errors.New(&quot;execution reverted&quot;)
  ErrMaxCodeSizeExceeded      = errors.New(&quot;max code size exceeded&quot;)
  ErrInvalidJump              = errors.New(&quot;invalid jump destination&quot;)
  ErrWriteProtection          = errors.New(&quot;write protection&quot;)
  ErrReturnDataOutOfBounds    = errors.New(&quot;return data out of bounds&quot;)
  ErrGasUintOverflow          = errors.New(&quot;gas uint64 overflow&quot;)
  ErrInvalidCode              = errors.New(&quot;invalid code: must not begin with 0xef&quot;)
  ErrStackUnderflow           = errors.New(fmt.Sprintf(&quot;stack underflow (%d &lt;=&gt; %d)&quot;, e.stackLen, e.required))
  ErrStackOverflow            = errors.New(fmt.Sprintf(&quot;stack limit reached %d (%d)&quot;, e.stackLen, e.limit))
  ErrInvalidOpCode            = errors.New(fmt.Sprintf(&quot;invalid opcode: %s&quot;, e.opcode))
)</code></pre>
<h4>9.4.3 Jump Destionation Validity 跳转地址验证</h4>
<p>公式 (142) (143) (144)</p>
<pre><code class="language-katex">D(\mathbf{c}) \equiv D_{\mathrm{J}}(\mathbf{c}, 0)</code></pre>
<p>where:</p>
<pre><code class="language-katex">D_{\mathrm{J}}(\mathbf{c}, i) \equiv \begin{cases}
\{\} &amp; \text{if} \quad i \geqslant \lVert \mathbf{c} \rVert  \\
\{ i \} \cup D_{\mathrm{J}}(\mathbf{c}, N(i, \mathbf{c}[i])) &amp; \text{if} \quad \mathbf{c}[i] = \text{\small JUMPDEST} \\
D_{\mathrm{J}}(\mathbf{c}, N(i, \mathbf{c}[i])) &amp; \text{otherwise} \\
\end{cases}</code></pre>
<pre><code class="language-katex">N(i, w) \equiv \begin{cases}
i + w - \text{\small PUSH1} + 2 &amp; \text{if} \quad w \in [\text{\small PUSH1}, \text{\small PUSH32}] \\
i + 1 &amp; \text{otherwise} \end{cases}</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">\mathbf{c}</code>
<ul>
<li>当前执行中的一段代码</li>
</ul>
</li>
<li><code class="katex-inline">D</code> TODO
<ul>
<li>计算 <code class="katex-inline">\mathbf{c}</code> 的有效跳转地址的集合</li>
<li>按 <code class="katex-inline">{\small JUMPDEST}</code> 指令的位置来定义</li>
</ul>
</li>
<li><code class="katex-inline">N</code> TODO
<ul>
<li>下一有效指令在代码 <code class="katex-inline">\mathbf{c}</code> 中的位置</li>
<li>且忽略 <code class="katex-inline">\text{\small PUSH1}</code> 指令的数据(如果有的话)</li>
</ul>
</li>
</ul>
<h4>9.4.4 Normal Halting</h4>
<p>(145) 正常停止检查函数 <code class="katex-inline">H</code></p>
<pre><code class="language-katex">H(\boldsymbol{\mu}, I) \equiv \begin{cases}
H_{\text{\tiny RETURN}}(\boldsymbol{\mu}) &amp; \text{if} \quad w \in \{\text{\small {RETURN}}, \text{\small REVERT}\} &amp;\\
() &amp; \text{if} \quad w \in \{ \text{\small {STOP}}, \text{\small {SELFDESTRUCT}} \} &amp;\\
\varnothing &amp; \text{otherwise}
\end{cases}</code></pre>
<p>有3种可能的输出</p>
<p>其中</p>
<ul>
<li><code class="katex-inline">H_{\text{\tiny RETURN}}</code>
<ul>
<li>表示有意识地停止执行并附加执行结果 <code class="katex-inline">o</code></li>
<li>参考 <a href="#appendix-h-virtual-machine-specification"># Appendix H. Virtual Machine Specification</a></li>
<li><code class="katex-inline">H_{\text{\tiny RETURN}}(\boldsymbol{\mu}) \equiv \boldsymbol{\mu}_{\mathbf{m}}[ \boldsymbol{\mu}_{\mathbf{s}}[0] \dots ( \boldsymbol{\mu}_{\mathbf{s}}[0] + \boldsymbol{\mu}_{\mathbf{s}}[1] - 1 ) ]</code></li>
</ul>
</li>
<li><code class="katex-inline">()</code> 空集
<ul>
<li>表示停止执行</li>
</ul>
</li>
<li><code class="katex-inline">\varnothing</code> 空
<ul>
<li>表示继续执行</li>
</ul>
</li>
</ul>
<h3>9.5 The Execution Cycle 执行循环</h3>
<p>公式 (146) (147) (148) (149)</p>
<pre><code class="language-katex">\begin{aligned}
O\big((\boldsymbol{\sigma}, \boldsymbol{\mu}, A, I)\big) &amp; \quad\equiv\quad (\boldsymbol{\sigma}&#039;, \boldsymbol{\mu}&#039;, A&#039;, I) \\
\Delta &amp; \quad\equiv\quad \mathbf{\alpha}_{w} - \mathbf{\delta}_{w} \\
\lVert\boldsymbol{\mu}&#039;_{\mathbf{s}}\rVert &amp; \quad\equiv\quad \lVert\boldsymbol{\mu}_{\mathbf{s}}\rVert + \Delta \\
\quad \forall x \in [\mathbf{\alpha}_{w}, \lVert\boldsymbol{\mu}&#039;_{\mathbf{s}}\rVert): \boldsymbol{\mu}&#039;_{\mathbf{s}}[x] &amp; \quad\equiv\quad \boldsymbol{\mu}_{\mathbf{s}}[x-\Delta]
\end{aligned}</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">\delta</code>
<ul>
<li>指令 <code class="katex-inline">w</code> 的出栈个数</li>
</ul>
</li>
<li><code class="katex-inline">\alpha</code>
<ul>
<li>指令 <code class="katex-inline">w</code> 的入栈个数</li>
</ul>
</li>
<li><code class="katex-inline">\Delta</code>
<ul>
<li>栈大小的变化</li>
</ul>
</li>
<li>栈的索引
<ul>
<li>从上往下增长</li>
<li>栈顶为0</li>
</ul>
</li>
</ul>
<p>公式 (150) (151)</p>
<pre><code class="language-katex">\begin{aligned}
\boldsymbol{\mu}&#039;_{\mathrm{g}} &amp; \quad\equiv\quad \boldsymbol{\mu}_{\mathrm{g}} - C(\boldsymbol{\sigma}, \boldsymbol{\mu}, I) \\
\boldsymbol{\mu}&#039;_{\mathrm{pc}} &amp; \quad\equiv\quad \begin{cases}
{J_{\text{JUMP}}}(\boldsymbol{\mu}) &amp; \text{if} \quad w = \text{\small JUMP} \\
{J_{\text{JUMPI}}}(\boldsymbol{\mu}) &amp; \text{if} \quad w = \text{\small JUMPI} \\
N(\boldsymbol{\mu}_{\mathrm{pc}}, w) &amp; \text{otherwise}
\end{cases}
\end{aligned}</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">N</code>
<ul>
<li>参考 (144)</li>
</ul>
</li>
</ul>
<p>公式 (152) (153) (154) (155)</p>
<pre><code class="language-katex">\begin{aligned}
\boldsymbol{\mu}&#039;_{\mathbf{m}} &amp; \quad\equiv\quad \boldsymbol{\mu}_{\mathbf{m}} \\
\boldsymbol{\mu}&#039;_{\mathrm{i}} &amp; \quad\equiv\quad \boldsymbol{\mu}_{\mathrm{i}} \\
A&#039; &amp; \quad\equiv\quad A \\
\boldsymbol{\sigma}&#039; &amp; \quad\equiv\quad \boldsymbol{\sigma}
\end{aligned}</code></pre>
<p>通常我们假定内存(<code class="katex-inline">\boldsymbol{\mu}'_{\mathbf{m}}</code>, <code class="katex-inline">\boldsymbol{\mu}'_{\mathbf{i}}</code>)，自毁集合，世界状态不会被修改</p>
<p>具体参考 <a href="#appendix-h-virtual-machine-specification"># Appendix H. Virtual Machine Specification</a></p>
<h2>10. Blocktree to Blockchain 区块树到区块链</h2>
<p>(156) (157) 区块难度计算函数</p>
<pre><code class="language-katex">\begin{aligned}
B_{\mathrm{t}} &amp; \equiv B&#039;_{\mathrm{t}} + B_{\mathrm{d}} \\
B&#039; &amp; \equiv P(B_{\mathrm{H}})
\end{aligned}</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">B</code>
<ul>
<li>一个区块</li>
</ul>
</li>
<li><code class="katex-inline">B'</code>
<ul>
<li>父区块</li>
</ul>
</li>
<li><code class="katex-inline">B_{\mathrm{t}}</code>
<ul>
<li>区块总难度</li>
</ul>
</li>
<li><code class="katex-inline">B_{\mathrm{d}}</code>
<ul>
<li>当前区块的难度</li>
</ul>
</li>
</ul>
<h2>11. Block Finalization 区块定稿</h2>
<p>区块定稿要经过 4 步验证</p>
<ul>
<li>验证叔块 Ommer Validation</li>
<li>验证交易 Transaction Validation</li>
<li>奖励发放 Reward Application</li>
<li>验证状态和 nonce State &amp; Nonce Validation</li>
</ul>
<h3>11.1 Ommer Validation 验证 Ommer</h3>
<p>(158) (159) (160)</p>
<pre><code class="language-katex">\lVert B_{\mathbf{U}} \rVert \leqslant 2 \bigwedge_{\mathbf{U} \in B_{\mathbf{U}}} {V({\mathbf{U}}})\; \wedge \; k({\mathbf{U}}, P(\mathbf{B}_{\mathbf{H}})_{\mathbf{H}}, 6) \\</code></pre>
<pre><code class="language-katex">k(U, H, n) \equiv \begin{cases} \mathit{false} &amp; \text{if} \quad n = 0 \\
s(U, H) \vee \; k(U, P(H)_{\mathrm{H}}, n - 1) &amp; \text{otherwise}
\end{cases}</code></pre>
<pre><code class="language-katex">s(U, H) \equiv (P(H) = P(U)\; \wedge \; H \neq U \; \wedge \; U \notin B(H)_{\mathbf{U}})</code></pre>
<p>验证</p>
<ul>
<li>当前区块头最多有2个叔块</li>
<li>叔块头列表自身有效，且表示的叔块与当前区块在6代以内</li>
</ul>
<p>其中</p>
<ul>
<li><code class="katex-inline">k</code>
<ul>
<li>is-kin 是亲属</li>
</ul>
</li>
<li><code class="katex-inline">s</code>
<ul>
<li>is-sibling 是兄妹</li>
</ul>
</li>
<li><code class="katex-inline">V</code>
<ul>
<li>区块头验证函数</li>
<li>参考公式 (51)</li>
</ul>
</li>
</ul>
<h3>11.2 Transaction Validation 交易验证</h3>
<pre><code class="language-katex">{B_{\mathrm{H}}}_{\mathrm{g}} = {\ell}({\mathbf{R})_{\mathrm{u}}}</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">{B_{\mathrm{H}}}_{\mathrm{g}}</code>
<ul>
<li>当前区块头中的累计使用 gas</li>
</ul>
</li>
<li><code class="katex-inline">{\ell}({\mathbf{R})_{\mathrm{u}}}</code>
<ul>
<li>当前区块中最后一条收据的累计使用 gas</li>
</ul>
</li>
</ul>
<h3>11.3 Reward Application 奖励发放</h3>
<p>公式 (162) (163) (164) (165) (166) <code class="katex-inline">\Omega</code> 区块奖励函数</p>
<pre><code class="language-katex">\begin{aligned}
\Omega(B, \boldsymbol{\sigma}) &amp; \quad\equiv\quad \boldsymbol{\sigma}&#039;: \boldsymbol{\sigma}&#039; = \boldsymbol{\sigma} \quad \text{except:} \\
\qquad\boldsymbol{\sigma}&#039;[{\mathbf{B}_{\mathrm{H}}}_{\mathrm{c}}]_{\mathrm{b}} &amp; \quad=\quad \boldsymbol{\sigma}[{\mathbf{B}_{\mathrm{H}}}_{\mathrm{c}}]_{\mathrm{b}} + \left(1 + \frac{\lVert \mathbf{B}_{\mathbf{U}}\rVert}{32}\right)R_{\mathrm{block}} \\
\forall \mathbf{U} \in \mathbf{B}_{\mathbf{U}}:\quad
\boldsymbol{\sigma}&#039;[\mathbf{U}_{\mathrm{c}}] &amp; \quad=\quad \begin{cases}
\varnothing &amp;\text{if}\ \boldsymbol{\sigma}[\mathbf{U}_{\mathrm{c}}] = \varnothing\ \wedge\ R = 0 \\
\mathbf{a}&#039; &amp;\text{otherwise}
\end{cases} \\
\mathbf{a}&#039; &amp; \quad\equiv\quad (\boldsymbol{\sigma}[U_{\mathrm{c}}]_{\mathrm{n}}, \boldsymbol{\sigma}[U_{\mathrm{c}}]_{\mathrm{b}} + R, \boldsymbol{\sigma}[U_{\mathrm{c}}]_{\mathbf{s}}, \boldsymbol{\sigma}[U_{\mathrm{c}}]_{\mathrm{c}}) \\
R &amp; \quad\equiv\quad \left(1 + \frac{1}{8} (U_{\mathrm{i}} - {B_{\mathrm{H}}}_{\mathrm{i}})\right) R_{\mathrm{block}}
\end{aligned}</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">\mathbf{B}_{\mathbf{U}}</code>
<ul>
<li>当前区块内的叔块头列表</li>
</ul>
</li>
<li><code class="katex-inline">{\mathbf{B}_{\mathrm{H}}}_{\mathrm{c}}</code>
<ul>
<li>当前区块头中存储的 beneficiary</li>
<li>即当前区块收益的归属地址</li>
</ul>
</li>
<li><code class="katex-inline">U_{\mathrm{c}}</code>
<ul>
<li>叔块收益的归属地址</li>
</ul>
</li>
</ul>
<p>(167) <code class="katex-inline">{R_{block}}</code> 定义</p>
<pre><code class="language-katex">R_{\mathrm{block}} = 10^{18} \times \begin{cases}
5 &amp;\text{if}\ H_{\mathrm{i}} &lt; F_{\mathrm{Byzantium}} \\
3 &amp;\text{if}\ F_{\mathrm{Byzantium}} \leqslant H_{\mathrm{i}} &lt; F_{\mathrm{Constantinople}} \\
2 &amp;\text{if}\ H_{\mathrm{i}} \geqslant F_{\mathrm{Constantinople}} \\
\end{cases} \\</code></pre>
<h3>11.4 State &amp; Nonce Validation 状态和 nonce 验证</h3>
<p>(168) 映射 Block 到世界状态的函数 <code class="katex-inline">\Gamma</code></p>
<pre><code class="language-katex">\Gamma(B) \equiv \begin{cases}
\boldsymbol{\sigma}_0 &amp; \text{if} \quad P(B_{\mathrm{H}}) = \varnothing \\
\boldsymbol{\sigma}_{\mathrm{i}}: \mathtt{{TRIE}}(L_{\mathrm{S}}(\boldsymbol{\sigma}_{\mathrm{i}})) = {P(B_{\mathrm{H}})_{\mathrm{H}}}_{\mathrm{r}} &amp; \text{otherwise}
\end{cases}</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">L_S</code>
<ul>
<li>世界状态坍塌函数</li>
<li>参考 (10)</li>
</ul>
</li>
<li><code class="katex-inline">\mathtt{{TRIE}}(L_{\mathrm{S}}(\boldsymbol{\sigma}_{\mathrm{i}})) = {P(B_{\mathrm{H}})_{\mathrm{H}}}_{\mathrm{r}}</code>
<ul>
<li>判断 state TRIE 根节点的内容 等于 区块头的 stateRoot</li>
<li>理解
<ul>
<li>不像 Block 存储在区块链上，TRIE 树结构是通过存储在客户端数据库中的数据构造出来的</li>
<li>因此需要比较区块头存储中的 stateRoot hash，是否与构造 TRIE 树时层层计算出来的根节点 hash 相等</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>(169) (170) (171) (172) 区块级别状转换函数 <code class="katex-inline">\Phi</code></p>
<pre><code class="language-katex">\begin{aligned}
\Phi(B) &amp; \quad\equiv\quad B&#039;: \quad B&#039; = B^* \quad \text{except:} \\
B&#039;_{\mathrm{n}} &amp; \quad=\quad n: \quad x \leqslant \frac{2^{256}}{{H_{\mathrm{d}}}} \\
B&#039;_{\mathrm{m}} &amp; \quad=\quad m \quad \text{with } (x, m) = \mathtt{PoW}(B^*_{{\cancel{n}}}, n, \mathbf{d}) \\
B^* &amp; \quad\equiv\quad B \quad \text{except:} \quad {{B^*_{\mathrm{r}}}} = {r}({\Pi}(\Gamma(B), B))
\end{aligned}</code></pre>
<p><code class="katex-inline">\Phi</code> 函数计算 nonce 和 mixHash 后分别设置到 <code class="katex-inline">B'_{\mathrm{n}}</code> 和 <code class="katex-inline">B'_{\mathrm{m}}</code></p>
<p>其中</p>
<ul>
<li><code class="katex-inline">\mathbf{d}</code>
<ul>
<li>参考 <a href="#appendix-j-ethhash"># Appendix J. EthHash</a></li>
</ul>
</li>
<li><code class="katex-inline">\Pi</code>
<ul>
<li>区块级状态转换函数</li>
<li>参考公式 (177)</li>
</ul>
</li>
<li><code class="katex-inline">\Omega</code>
<ul>
<li>区块奖励函数</li>
</ul>
</li>
</ul>
<p>(173) 第 n 个状态 <code class="katex-inline">\boldsymbol{\sigma}[n]</code></p>
<pre><code class="language-katex">\boldsymbol{\sigma}[n] = \begin{cases} {\Gamma}(B) &amp; \text{if} \quad n &lt; 0 \\ {\Upsilon}(\boldsymbol{\sigma}[n - 1], B_{\mathbf{T}}[n]) &amp; \text{otherwise} \end{cases}</code></pre>
<p>(174) (175) (176) <code class="katex-inline">\Upsilon^{\mathbf{u}}</code> <code class="katex-inline">\Upsilon^{\mathbf{l}}</code> <code class="katex-inline">\Upsilon^{\mathbf{z}}</code> 的定义/赋值</p>
<pre><code class="language-katex">\begin{aligned}
\mathbf{R}[n]_{\mathrm{u}} = \begin{cases} 0 &amp; \text{if} \quad n &lt; 0 \\
\Upsilon^g(\boldsymbol{\sigma}[n - 1], B_{\mathbf{T}}[n]) \quad + \mathbf{R}[n-1]_{\mathrm{u}} &amp; \text{otherwise} \end{cases}
\end{aligned}</code></pre>
<pre><code class="language-katex">\mathbf{R}[n]_{\mathbf{l}} =
\Upsilon^{\mathbf{l}}(\boldsymbol{\sigma}[n - 1], B_{\mathbf{T}}[n])</code></pre>
<pre><code class="language-katex">\mathbf{R}[n]_{\mathrm{z}} =
\Upsilon^{\mathrm{z}}(\boldsymbol{\sigma}[n - 1], B_{\mathbf{T}}[n])</code></pre>
<p>其中</p>
<ul>
<li><code class="katex-inline">R_{\mathrm{u}}</code>
<ul>
<li>当前区块中，交易发生后的累积 gas 使用量</li>
</ul>
</li>
<li><code class="katex-inline">R_{\mathrm{l}}</code>
<ul>
<li>交易过程中创建的一系列日志</li>
</ul>
</li>
<li><code class="katex-inline">R_{\mathrm{z}}</code>
<ul>
<li>交易的状态码</li>
</ul>
</li>
<li><code class="katex-inline">R_{\mathrm{b}}</code>
<ul>
<li>由一系列日志构成的 Bloom 过滤器</li>
<li>参考公式 (26) (27) (28) (29) (30)</li>
</ul>
</li>
</ul>
<p>公式 (177) 区块级状态转换函数 <code class="katex-inline">\Pi</code></p>
<pre><code class="language-katex">\Pi(\boldsymbol{\sigma}, B) \equiv {\Omega}(B, \ell(\boldsymbol{\sigma}))</code></pre>
<p>对区块应用区块奖励函数 <code class="katex-inline">{\Omega}</code>，可以得到最新的世界状态，即最终的区块级状态转换</p>
<h2>Appendix B. Recursive Length Prefix</h2>
<p>参考 <a href="https://eth.wiki/en/fundamentals/rlp">eth wiki: RLP</a></p>
<h2>Appendix C. Hex-Prefix Encoding</h2>
<p>公式 (189) (190)</p>
<pre><code class="language-katex">\begin{aligned}
\mathtt{HP}(\mathbf{x}, t): \mathbf{x} \in \mathbb{Y} \equiv \begin{cases}
(16f(t), 16\mathbf{x}[0] + \mathbf{x}[1], 16\mathbf{x}[2] + \mathbf{x}[3], ...) &amp;
\text{if} \lVert \mathbf{x} \rVert \; \text{is even} \\
(16(f(t) + 1) + \mathbf{x}[0], 16\mathbf{x}[1] + \mathbf{x}[2], 16\mathbf{x}[3] + \mathbf{x}[4], ...) &amp; \text{otherwise}
\end{cases} \\
\end{aligned}</code></pre>
<pre><code class="language-katex">\begin{aligned}
f(t) &amp; \equiv &amp; \begin{cases} 2 &amp; \text{if} \quad t \neq 0 \\ 0 &amp; \text{otherwise} \end{cases}
\end{aligned}</code></pre>
<p>参考</p>
<ul>
<li><a href="https://medium.com/coinmonks/data-structure-in-ethereum-episode-1-compact-hex-prefix-encoding-12558ae02791">Data structure in Ethereum | Episode 1+: Compact (Hex-prefix) encoding.</a></li>
</ul>
<pre><code class="language-go">// https://github.com/ethereum/go-ethereum/blob/master/trie/encoding.go

// https://github.com/sontuphan/debug-geth/blob/master/trie/encoding.go
// This block of code does Compact (hex-prefix) encoding as following table
// hex char    bits    |    node type partial    path length
// 0           0000    |    extension            even
// 1           0001    |    extension            odd
// 2           0010    |    terminating (leaf)   even
// 3           0011    |    terminating (leaf)   odd

func hexToCompact(hex []byte) []byte {
    terminator := byte(0)
    if hasTerm(hex) {
        terminator = 1
        hex = hex[:len(hex)-1]
    }
    buf := make([]byte, len(hex)/2+1)
    buf[0] = terminator &lt;&lt; 5 // the flag byte
    if len(hex)&amp;1 == 1 {
        buf[0] |= 1 &lt;&lt; 4 // odd flag
        buf[0] |= hex[0] // first nibble is contained in the first byte
        hex = hex[1:]
    }
    decodeNibbles(hex, buf[1:])
    return buf
}</code></pre>
<ul>
<li>输入
<ul>
<li>hex</li>
<li>16进制明文字符串 path
<ul>
<li>每个字符占用一个字节，取值范围是 [0-9, a-f]</li>
</ul>
</li>
<li>可能在 path 尾部追加最后一个字节，值为 0x10，表示 terminator
<ul>
<li>即 hex 是个叶子节点(包含 value)，而非中间节点</li>
</ul>
</li>
</ul>
</li>
<li>输出
<ul>
<li>buf (Hex-Prefix 编码得到的二进制字符串 )</li>
<li>添加半个字节(一个hex编码)到 hex 前面，用于表示
<ul>
<li>hex 是否包含 terminator</li>
<li>hex 移除 terminator 后(如果有的话)，长度是奇数还是偶数</li>
</ul>
</li>
<li>确保 buf 的长度是偶数</li>
</ul>
</li>
</ul>
<p>例子</p>
<table>
<thead>
<tr>
<th style="text-align: right;">Row</th>
<th style="text-align: left;">Node Type</th>
<th style="text-align: left;">Path Length</th>
<th style="text-align: left;">Path Before Encoding(In HEX)</th>
<th style="text-align: left;">Path After Encoding(In HEX)</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: right;">0</td>
<td style="text-align: left;">Extension</td>
<td style="text-align: left;">EVEN</td>
<td style="text-align: left;">[0, 1, 2, 3, 4, 5]</td>
<td style="text-align: left;">'00 01 23 45'</td>
</tr>
<tr>
<td style="text-align: right;">1</td>
<td style="text-align: left;">Extension</td>
<td style="text-align: left;">ODD</td>
<td style="text-align: left;">[1, 2, 3, 4, 5]</td>
<td style="text-align: left;">'11 23 45'</td>
</tr>
<tr>
<td style="text-align: right;">2</td>
<td style="text-align: left;">Leaf(has terminator(10))</td>
<td style="text-align: left;">EVEN</td>
<td style="text-align: left;">[0, f, 1, c, b, 8, 10]</td>
<td style="text-align: left;">'20 0f 1c b8'</td>
</tr>
<tr>
<td style="text-align: right;">3</td>
<td style="text-align: left;">Leaf(has terminator(10))</td>
<td style="text-align: left;">ODD</td>
<td style="text-align: left;">[f, 1, c, b, 8, 10]</td>
<td style="text-align: left;">'3f 1c b8'</td>
</tr>
</tbody>
</table>
<h2>Appendix D. Modified Merkle Patricia Tree</h2>
<p>参考</p>
<ul>
<li><a href="https://medium.com/shyft-network-media/understanding-trie-databases-in-ethereum-9f03d2c3325d">Understanding Trie Databases in Ethereum</a></li>
<li><a href="https://ethereum.stackexchange.com/questions/268/ethereum-block-architecture">Ethereum block architecture</a></li>
</ul>
<p>(191) (192) 数据集合 <code class="katex-inline">\mathfrak{I}</code></p>
<pre><code class="language-katex">\mathfrak{I} = \{ (\mathbf{k}_0 \in \mathbb{B}, \mathbf{v}_0 \in \mathbb{B}), (\mathbf{k}_1 \in \mathbb{B}, \mathbf{v}_1 \in \mathbb{B}), ... \} \\
\forall I \in \mathfrak{I}: I \equiv (I_0, I_1)</code></pre>
<p>(193) (194)</p>
<p>任何 bytes 都可以看为半字节(nibbles 4bit)组成的序列</p>
<pre><code class="language-katex">\begin{aligned}
y(\mathfrak{I}) &amp; = \{ (\mathbf{k}_0&#039; \in \mathbb{Y}, \mathbf{v}_0 \in \mathbb{B}), (\mathbf{k}_1&#039; \in \mathbb{Y}, \mathbf{v}_1 \in \mathbb{B}), ... \} \\
\forall n: \quad \forall i &lt; 2\lVert\mathbf{k}_{n}\rVert: \quad \mathbf{k}_{n}&#039;[i] &amp; \equiv
\begin{cases}
  \lfloor \mathbf{k}_{n}[i \div 2] \div 16 \rfloor &amp; \text{if} \; i \; \text{is even} \\
  \mathbf{k}_{n}[\lfloor i \div 2 \rfloor] \bmod 16 &amp; \text{otherwise}
\end{cases}
\end{aligned}</code></pre>
<p>公式 (195) 根节点函数 <code class="katex-inline">\texttt{TRIE}</code></p>
<pre><code class="language-katex">\texttt{TRIE}(\mathfrak{I}) \equiv \texttt{KEC}\big(\texttt{RLP} (c(\mathfrak{I}, 0))\big)</code></pre>
<p>公式 (196)</p>
<pre><code class="language-katex">n(\mathfrak{I}, i) \equiv \begin{cases}
() &amp; \text{if} \quad \mathfrak{I} = \varnothing \\
c(\mathfrak{I}, i) &amp; \text{if} \quad \lVert \, \texttt{RLP} \big( c(\mathfrak{I}, i) \big) \rVert &lt; 32 \\
\texttt{KEC}\big(\texttt{RLP}( c(\mathfrak{I}, i)) \big) &amp; \text{otherwise}
\end{cases}</code></pre>
<p>公式 (197)</p>
<pre><code class="language-katex">\begin{aligned}
c(\mathfrak{I}, i) \equiv \begin{cases}
  \big(\texttt{HP}(I_0[i .. (\lVert I_0\rVert - 1)], 1), I_1 \big) &amp; \text{if} \ \lVert \mathfrak{I} \rVert = 1 \text{where} \ \exists I: I \in \mathfrak{I} \\
  \big(\texttt{HP}(I_0[i .. (j - 1)], 0), n(\mathfrak{I}, j) \big) &amp; \text{if} \ i \ne j \  \text{where} \ j = \max \{ x : \exists \mathbf{l}: \lVert \mathbf{l} \rVert = x \wedge \forall I \in \mathfrak{I}: I_0[0 .. (x - 1)] = \mathbf{l} \} \\
  (u(0), u(1), ..., u(15), v) &amp; \text{otherwise} \ \text{where} \\
  &amp; u(j) \equiv n(\{ I : I \in \mathfrak{I} \wedge I_0[i] = j \}, i + 1) \\
  &amp; v = \begin{cases}
    I_1 &amp; \text{if} \ \exists I: I \in \mathfrak{I} \wedge \lVert I_0 \rVert = i \\
    () &amp; \text{otherwise}
  \end{cases}
\end{cases}
\end{aligned}</code></pre>
<p>其中</p>
<ul>
<li>叶子节点 Lea
<ul>
<li>包含两个字段</li>
<li>第一个字段是剩下的 Key 的 HP 编码(HP 的第二个参数为1)</li>
<li>第二个字段是 Value</li>
</ul>
</li>
<li>扩展节点 Extension
<ul>
<li>包含两个字段</li>
<li>第一个字段是剩下的 Key 的可以至少被两个剩下节点共享的最大公共前缀((HP 的第二个参数为0)</li>
<li>第二个字段是 <code class="katex-inline">n(\mathfrak{I}, j)</code></li>
</ul>
</li>
<li>分支节点 Branch
<ul>
<li>包含17个字段</li>
<li>前16个项目对应于 [0-9,a-f]</li>
<li>第17个字段是存储在当前结点结束的节点
<ul>
<li>例如</li>
<li>有三个key，分别是 abc，abd，ab</li>
<li>第17个字段储存了ab节点的值</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><img decoding="async" src="https://godorz.info/wp-content/uploads/2021/10/ethereum_blockchain_mechanism-scaled.jpg" alt="ethereum_blockchain_mechanism" /><br />
<img decoding="async" src="https://godorz.info/wp-content/uploads/2021/10/merkle_trie_tree.png" alt="merkle_trie_tree" /></p>
<p>图片来源 <a href="https://ethereum.stackexchange.com/questions/268/ethereum-block-architecture">ELI5 How does a Merkle-Patricia-trie tree work?</a></p>
<h2>Appendix H. Virtual Machine Specification</h2>
<p>注意区分单位</p>
<p><code class="katex-inline">\boldsymbol{\mu}_{\mathbf{s}}</code> / <code class="katex-inline">\boldsymbol{\sigma}[a]_{\mathbf{s}}</code> 以 32 字节为单位</p>
<p><code class="katex-inline">\boldsymbol{\mu}_{\mathbf{m}}</code> 以 1 字节为单位</p>
<p>参考 <code>SSTORE</code>/<code>SLOAD</code>, <code>MSTORE</code>/<code>MLOAD</code></p>
<h3>H.2. Instruction Set</h3>
<p>参考 <a href="https://ethereum.stackexchange.com/questions/3667/difference-between-call-callcode-and-delegatecall">Difference between CALL, CALLCODE and DELEGATECALL</a></p>
<blockquote>
<p>DELEGATECALL was a new opcode that was a bug fix for CALLCODE which did not preserve msg.sender and msg.value. If Alice invokes Bob who does DELEGATECALL to Charlie, the msg.sender in the DELEGATECALL is Alice (whereas if CALLCODE was used the msg.sender would be Bob).</p>
</blockquote>
]]></content:encoded>
					
					<wfw:commentRss>https://godorz.info/2021/10/ethereum-yellow-paper/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AAVE V2 学习笔记</title>
		<link>https://godorz.info/2021/10/aave-v2/</link>
					<comments>https://godorz.info/2021/10/aave-v2/#comments</comments>
		
		<dc:creator><![CDATA[ripwu]]></dc:creator>
		<pubDate>Fri, 22 Oct 2021 03:18:10 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[blockchain]]></category>
		<category><![CDATA[defi]]></category>
		<guid isPermaLink="false">http://godorz.info/?p=1690</guid>

					<description><![CDATA[这几天在学习 AAVE，资料看了 V1 和 V2 的白皮书，代码只看了 V2 版本，另外感谢大佬分享： AAVE v2 - white paper aave小组分享：白皮书解读 Dapp-Learning: AAVE 这里简单记下学习笔记 需要说明的是，个人不太适应白皮书自上而下的展开结构，所以笔记反向记录 利率 rate 当前时刻的浮动/稳定借款利率 variable/stable rate 公式 \#R_{t}^{asset} = \begin{aligned} \begin{cases} \#R_{base}^{asset} + \frac{U_{t}^{asset}}{U_{optimal}} \times \#R_{slope1}^{asset} &#38;\ if \ U_{t}^{asset} \lt U_{optimal} \\ \#R_{base}^{asset} + \#R_{slope1}^{asset} + \frac{U_{t}^{asset} - U_{optimal}}{1 - U_{optimal}} \times \#R_{slope2}^{asset} &#38;\ if \ U_{t}^{asset} [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>这几天在学习 AAVE，资料看了 V1 和 V2 的白皮书，代码只看了 V2 版本，另外感谢大佬分享：</p>
<p><a href="https://learnblockchain.cn/article/3099">AAVE v2 - white paper</a><br />
<a href="https://www.bilibili.com/video/BV1UF411Y7oU">aave小组分享：白皮书解读</a><br />
<a href="https://github.com/rebase-network/Dapp-Learning/blob/main/defi/Aave/contract/readme.md">Dapp-Learning: AAVE</a></p>
<p>这里简单记下学习笔记</p>
<p>需要说明的是，个人不太适应白皮书自上而下的展开结构，所以笔记反向记录</p>
<pre><code class="language-c"></code></pre>
<h2>利率 rate</h2>
<h3>当前时刻的浮动/稳定借款利率</h3>
<p>variable/stable rate</p>
<h4>公式</h4>
<pre><code class="language-katex">\#R_{t}^{asset} =
\begin{aligned}
\begin{cases}
\#R_{base}^{asset} + \frac{U_{t}^{asset}}{U_{optimal}} \times \#R_{slope1}^{asset} &amp;\ if \ U_{t}^{asset} \lt U_{optimal} \\
\#R_{base}^{asset} + \#R_{slope1}^{asset} + \frac{U_{t}^{asset} - U_{optimal}}{1 - U_{optimal}} \times \#R_{slope2}^{asset} &amp;\ if \ U_{t}^{asset} \geq U_{optimal}
\end{cases}
\end{aligned}</code></pre>
<p>这里的 <code class="katex-inline">\#</code> 可以为 <code>V</code> 或 <code>S</code>，代入后得到 <code class="katex-inline">VR_{t}</code> 或 <code class="katex-inline">SR_{t}</code>，分别表示浮动利率和稳定利率</p>
<p>换句话说，<code class="katex-inline">VR_{t}</code> 与 <code class="katex-inline">SR_{t}</code> 计算公式相同，只是系统参数不同</p>
<p>其中</p>
<p>资金利用率 等于 总债务 占 总储蓄 的比例</p>
<pre><code class="language-katex">U_{t}^{asset} =
\begin{aligned}
\begin{cases}
0 &amp;\ if \ L_{t}^{asset} = 0 \\
\frac{D_{t}^{asset}}{L_{t}^{asset}} &amp;\ if \ L_{t}^{asset} \gt 0
\end{cases}
\end{aligned}</code></pre>
<p>总债务 等于 浮动利率债务 与 稳定利率债务 之和</p>
<pre><code class="language-katex">D_{t}^{asset} = VD_{t}^{asset} + SD_{t}^{asset}</code></pre>
<p>综合上面三个公式，可以看到，利率 <code class="katex-inline">\#R_{t}</code> 与 资金利用率 <code class="katex-inline">U_{t}</code> 正相关</p>
<p>也就是说，借贷需求旺盛时，利率随着资金利用率上升；借贷需求萎靡时，利率随着资金利用率下降</p>
<h4>代码</h4>
<p>存储</p>
<pre><code class="language-js">library DataTypes {
  struct ReserveData {
    //the current variable borrow rate. Expressed in ray
    uint128 currentVariableBorrowRate;
    //the current stable borrow rate. Expressed in ray
    uint128 currentStableBorrowRate;
  }
}</code></pre>
<p>更新</p>
<pre><code class="language-js">interface IReserveInterestRateStrategy {
  function calculateInterestRates(
    address reserve,
    address aToken,
    uint256 liquidityAdded,
    uint256 liquidityTaken,
    uint256 totalStableDebt,
    uint256 totalVariableDebt,
    uint256 averageStableBorrowRate,
    uint256 reserveFactor
  )
    external
    view
    returns (
      uint256 liquidityRate,
      uint256 stableBorrowRate,
      uint256 variableBorrowRate
    );
}</code></pre>
<h3>最新浮动借款利率</h3>
<p>即上面的 <code class="katex-inline">VR_{t}</code></p>
<h3>稳定借款利率</h3>
<h4>平均稳定借款利率</h4>
<p>overall stable rate</p>
<h4>借款 <code>mint()</code></h4>
<p>假设利率为 <code class="katex-inline">SR_t</code> 时发生一笔额度为 <code class="katex-inline">SB_{new}</code> 的借款，则</p>
<pre><code class="language-katex">\overline{SR}_{t} = \frac{SD_{t} \times \overline{SR}_{t-1} + SB_{new} \times SR_{t}}{SD_{t} + SB_{new}}</code></pre>
<p><code class="katex-inline">SD_{t}</code> 表示此前债务 (不含 <code class="katex-inline">SB_{new}</code>) 到当前时刻累计的本金+利息（即 <code>previousSupply</code>）</p>
<p>在白皮书中没有公式，但应该是</p>
<pre><code class="language-katex">SD_{t} = SB \times (1+\frac{\overline{SR}_{t-1}}{T_{year}})^{\Delta T}</code></pre>
<p>--</p>
<p>用户 <code class="katex-inline">x</code> 的 平均利率 (用于还款时的计算)</p>
<pre><code class="language-katex">\overline{SR}(x) = \sum\nolimits_{i}^{}\frac{SR_{i}(x) \times SD_{i}(x)}{SD_{i}(x)}</code></pre>
<p>问题：未拆开 <code class="katex-inline">SD_{i-1}(x)</code> 与 <code class="katex-inline">SB_{new}</code></p>
<p>实际：需要拆开分别乘以不同的利率</p>
<p>结论：不应简单以 <code class="katex-inline">SD_{i}(x)</code> 代替</p>
<p>结合源码，应该修正为</p>
<pre><code class="language-katex">\overline{SR}_{t}(x) = \frac{SD_{t}(x) \times \overline{SR}_{t-1}(x) + SB_{new} \times SR_{t}}{SD_{t}(x) + SB_{new}}</code></pre>
<p>其中</p>
<pre><code class="language-katex">SD_{t}(x) = SB(x) \times (1+\frac{\overline{SR}_{t}}{T_{year}})^{\Delta T}</code></pre>
<p>结合源码，应该修正为</p>
<pre><code class="language-katex">SD_{t}(x) = SB(x) \times (1+\frac{\overline{SR}_{t-1}(x)}{T_{year}})^{\Delta T}</code></pre>
<p>比较 <code class="katex-inline">\overline{SR}_{t}</code> 和 <code class="katex-inline">\overline{SR}(x)</code> 两个公式</p>
<ul>
<li>(x) 强调 用户 <code class="katex-inline">x</code> 的平均利率，只受其自身操作的影响，不受其他用户影响</li>
</ul>
<p>比较 <code class="katex-inline">\overline{SR}(x)</code> 修正前后的两个公式</p>
<ul>
<li>原公式不出现 <code class="katex-inline">t</code> 调强 用户 <code class="katex-inline">x</code> 的平均利率，自上次操作后，不受时间影响</li>
<li>修正后更好体现 rebalancing</li>
</ul>
<h4>还款 <code>burn()</code></h4>
<p>假设用户平均利率为 <code class="katex-inline">\overline{SR}(x)</code> 时发生一笔额度为 <code class="katex-inline">SB(x)</code> 的还款，则</p>
<pre><code class="language-katex">\overline{SR}_{t} =
\begin{aligned}
\begin{cases}
0 &amp;\ if \ SD - SB(x) = 0 \\
\frac{SD_{t} \times \overline{SR}_{t-1} - SB(x) \times \overline{SR}(x)}{SD_t - SB(x)} &amp;\ if \ SD - SB(x) \gt 0
\end{cases}
\end{aligned}</code></pre>
<h4>代码</h4>
<p>存储</p>
<pre><code class="language-js">contract StableDebtToken is IStableDebtToken, DebtTokenBase {
  uint256 internal _avgStableRate; // 池子平均利率

  mapping(address =&gt; uint40) internal _timestamps; // 用户上次借款时间
  mapping(address =&gt; uint256) internal _usersStableRate; // 用户平均利率
}</code></pre>
<p>更新</p>
<p><code>StableDebtToken.mint()</code> 更新 <code>_avgStableRate</code>，<code>_timestamps[user]</code> 和 <code>_userStableRate[user]</code></p>
<p><code>StableDebtToken.burn()</code> 更新 <code>_avgStableRate</code>，<code>_timestamps[user]</code>，如果还款金额未超过利息，则将剩余利息作为新增借款，进行 <code>mint()</code> 这将修改 <code>_userStableRate[user]</code>，也是 rebalancing</p>
<p>TODO 举例</p>
<pre><code class="language-js">function burn(address user, uint256 amount) external override onlyLendingPool {
    // Since the total supply and each single user debt accrue separately,
    // there might be accumulation errors so that the last borrower repaying
    // mght actually try to repay more than the available debt supply.
    // In this case we simply set the total supply and the avg stable rate to 0

    // For the same reason described above, when the last user is repaying it might
    // happen that user rate * user balance &gt; avg rate * total supply. In that case,
    // we simply set the avg rate to 0
}</code></pre>
<h3>平均借款利率</h3>
<p>overall borrow rate</p>
<pre><code class="language-katex">\overline{R_{t}}^{asset} =
\begin{aligned}
\begin{cases}
0 &amp;\ if \ D_t=0 \\
\frac{VD_t \times VR_t + SD_t \times \overline{SR}_t}{D_t} &amp;\ if \ D_t &gt; 0
\end{cases}
\end{aligned}</code></pre>
<h3>当前时刻的流动性利率</h3>
<p>current liquidity rate</p>
<p>流动性利率 = 平均借款利率 X 资金利用率</p>
<pre><code class="language-katex">LR_{t}^{asset} = \overline{R}_{t} \times U_{t}</code></pre>
<p>结合 平均借款利率 和 资金利用率 的公式</p>
<pre><code class="language-katex">\overline{R_{t}}^{asset} =
\begin{aligned}
\begin{cases}
0 &amp;\ if \ D_t=0 \\
\frac{VD_t \times VR_t + SD_t \times \overline{SR}_t}{D_t} &amp;\ if \ D_t &gt; 0
\end{cases}
\end{aligned}</code></pre>
<pre><code class="language-katex">U_{t}^{asset} =
\begin{aligned}
\begin{cases}
0 &amp;\ if \ L_{t}^{asset} = 0 \\
\frac{D_{t}^{asset}}{L_{t}^{asset}} &amp;\ if \ L_{t}^{asset} \gt 0
\end{cases}
\end{aligned}</code></pre>
<p>同时考虑 超额抵押 的隐藏要求</p>
<pre><code class="language-katex">D_{t} &lt; L_{t}</code></pre>
<p>得到</p>
<pre><code class="language-katex">LR_{t}^{asset} =
\begin{aligned}
\begin{cases}
0 &amp;\ if \ L_{t} = 0 \ or \ D_{t} = 0\\
\frac{VD_t \times VR_t + SD_t \times \overline{SR}_t}{L_{t}} &amp;\ if \ L_{t} \gt 0
\end{cases}
\end{aligned}</code></pre>
<p>理解如下</p>
<p>分子 <code class="katex-inline">VR_{t}</code> 与 <code class="katex-inline">SR_{t}</code> 都是年化利率，所以 <code class="katex-inline">LR_{t}</code> 也是年化流动性利率</p>
<p>分母 <code class="katex-inline">L_{t}</code> 表示池子总存款本金，所以 <code class="katex-inline">LR_{t}</code> 表示 每单位存款本金，产生的借款利息</p>
<h2>指数 index</h2>
<h3>累计流动性指数</h3>
<p>cumulated liquidity index</p>
<p>从池子首次发生用户操作时，累计到现在，每单位存款本金，变成多少本金（含利息收入）</p>
<p>cumulated liquidity index</p>
<pre><code class="language-katex">LI_t=(LR_t \times \Delta T_{year} + 1) \times LI_{t-1} \\
LI_0=1 \times 10 ^{27} = 1 \ ray</code></pre>
<p>其中</p>
<pre><code class="language-katex">\Delta T_{year} = \frac{\Delta T}{T_{year}} = \frac{T - T_{l}}{T_{year}}</code></pre>
<p><code class="katex-inline">T_{l}</code> 池子最近一次发生用户操作的的时间</p>
<p><code class="katex-inline">T_{l}</code> is updated every time a borrow, deposit, redeem, repay, swap or liquidation event occurs.</p>
<p>--</p>
<p>每单位存款本金，未来将变成多少本金（含利息收入）</p>
<p>reserve normalized income</p>
<pre><code class="language-katex">NI_t = (LR_t \times \Delta T_{year} + 1) \times LI_{t-1}</code></pre>
<p>存储</p>
<pre><code class="language-js">  struct ReserveData {
    //the liquidity index. Expressed in ray
    uint128 liquidityIndex;
  }</code></pre>
<p>更新</p>
<pre><code class="language-js">  function _updateIndexes(
    DataTypes.ReserveData storage reserve,
    uint256 scaledVariableDebt,
    uint256 liquidityIndex,
    uint256 variableBorrowIndex,
    uint40 timestamp
  ) internal returns (uint256, uint256) {
    uint256 currentLiquidityRate = reserve.currentLiquidityRate;
    uint256 newLiquidityIndex = liquidityIndex;

    //only cumulating if there is any income being produced
    if (currentLiquidityRate &gt; 0) {
      uint256 cumulatedLiquidityInterest =
        MathUtils.calculateLinearInterest(currentLiquidityRate, timestamp);

      newLiquidityIndex = cumulatedLiquidityInterest.rayMul(liquidityIndex);
      require(newLiquidityIndex &lt;= type(uint128).max, Errors.RL_LIQUIDITY_INDEX_OVERFLOW);

      reserve.liquidityIndex = uint128(newLiquidityIndex);
    }

    reserve.lastUpdateTimestamp = uint40(block.timestamp);
  }
}</code></pre>
<h3>累计浮动借款指数</h3>
<p>cumulated variable borrow index</p>
<p>从池子首次发生用户操作时，累计到现在，每单位借款债务，共变成多少债务</p>
<p>cumulated variable borrow index</p>
<pre><code class="language-katex">VI_{t} = (1 + \frac{VR_t}{T_{year}})^{\Delta T} \times VI_{t-1}</code></pre>
<p>--</p>
<p>每单位浮动借款债务，未来将变成多少债务</p>
<p>normalised variable (cumulated) debt</p>
<pre><code class="language-katex">VN_{t} = (1 + \frac{VR_{t}}{T_{year}})^{\Delta T} \times VI_{t-1}</code></pre>
<p>存储</p>
<pre><code class="language-js">struct ReserveData {
    uint128 variableBorrowIndex;
}</code></pre>
<p>计算</p>
<pre><code class="language-js">// ReserveLogic.sol

/**
* @dev Returns the ongoing normalized variable debt for the reserve
* A value of 1e27 means there is no debt. As time passes, the income is accrued
* A value of 2*1e27 means that for each unit of debt, one unit worth of interest has been accumulated
* @return The normalized variable debt. expressed in ray
**/
function getNormalizedDebt(DataTypes.ReserveData storage reserve) internal view returns (uint256) {
    uint40 timestamp = reserve.lastUpdateTimestamp;

    if (timestamp == uint40(block.timestamp)) {
        return reserve.variableBorrowIndex;
    }

    uint256 cumulated =
        MathUtils.calculateCompoundedInterest(reserve.currentVariableBorrowRate, timestamp).rayMul(
        reserve.variableBorrowIndex
        );

    return cumulated;
}</code></pre>
<p>更新</p>
<pre><code class="language-js">  function _updateIndexes(
    DataTypes.ReserveData storage reserve,
    uint256 scaledVariableDebt,
    uint256 liquidityIndex,
    uint256 variableBorrowIndex,
    uint40 timestamp
  ) internal returns (uint256, uint256) {
    uint256 currentLiquidityRate = reserve.currentLiquidityRate;

    uint256 newVariableBorrowIndex = variableBorrowIndex;

    //only cumulating if there is any income being produced
    if (currentLiquidityRate &gt; 0) {
      //as the liquidity rate might come only from stable rate loans, we need to ensure
      //that there is actual variable debt before accumulating
      if (scaledVariableDebt != 0) {
        uint256 cumulatedVariableBorrowInterest =
          MathUtils.calculateCompoundedInterest(reserve.currentVariableBorrowRate, timestamp);
        newVariableBorrowIndex = cumulatedVariableBorrowInterest.rayMul(variableBorrowIndex);

        require(
          newVariableBorrowIndex &lt;= type(uint128).max,
          Errors.RL_VARIABLE_BORROW_INDEX_OVERFLOW
        );

        reserve.variableBorrowIndex = uint128(newVariableBorrowIndex);
      }
    }

    reserve.lastUpdateTimestamp = uint40(block.timestamp);
    return (newLiquidityIndex, newVariableBorrowIndex);
  }</code></pre>
<h3>用户累计浮动借款指数</h3>
<p>user cumulated variable borrow index</p>
<pre><code class="language-katex">VI(x) = VI_t(x)</code></pre>
<p>因为是浮动利率，所以用户每次发生借款的利率，都是当时最新的浮动借款利率</p>
<h2>代币 token</h2>
<h3>aToken</h3>
<p><code class="katex-inline">ScB_{t}(x)</code> 用户 <code class="katex-inline">x</code> 在 <code class="katex-inline">t</code> 时刻发生存款或取回操作后，在 ERC20 Token 中记录的 balance 债务</p>
<p>存款</p>
<pre><code class="language-katex">ScB_{t}(x) = ScB_{t-1} + \frac{m}{NI_{t}}</code></pre>
<p>取回</p>
<pre><code class="language-katex">ScB_{t}(x) = ScB_{t-1} - \frac{m}{NI_{t}}</code></pre>
<p>在当前最新的 <code class="katex-inline">t</code> 时刻看，用户 <code class="katex-inline">x</code> 的总余额</p>
<pre><code class="language-katex">aB_{t}(x) = ScB_{t}(x) \times NI_{t}</code></pre>
<h3>Debt token</h3>
<h4>浮动借款代币</h4>
<p>Variable debt token</p>
<p><code class="katex-inline">ScVB_t(x)</code> 用户 <code class="katex-inline">x</code> 在 <code class="katex-inline">t</code> 时刻发生借款或还款操作后，在 ERC20 Token 中记录的 balance 债务</p>
<p>借款</p>
<pre><code class="language-katex">ScVB_t(x) = ScVB_{t-1}(x) + \frac{m}{VN_{t}}</code></pre>
<p>还款</p>
<pre><code class="language-katex">ScVB_t(x) = ScVB_{t-1}(x) - \frac{m}{VN_{t}}</code></pre>
<p>在当前最新的 <code class="katex-inline">t</code> 时刻看，用户 <code class="katex-inline">x</code> 的总债务</p>
<pre><code class="language-katex">VD(x) = ScVB(x) \times D_{t}</code></pre>
<p>这里是个比较明显的 typo，应该修正为</p>
<pre><code class="language-katex">VD(x) = ScVB(x) \times VN_{t}</code></pre>
<h4>稳定借款代币</h4>
<p>Stable debt token</p>
<p>用户 <code class="katex-inline">x</code> 此前在 <code class="katex-inline">t</code> 发生操作后</p>
<pre><code class="language-katex">SD_{t}(x) = SB(x) \times (1+\frac{\overline{SR}_{t-1}(x)}{T_{year}})^{\Delta T}</code></pre>
<h2>风险</h2>
<h3>借贷利率参数</h3>
<p>Borrow Interest Rate</p>
<p>参考 <a href="https://docs.aave.com/risk/liquidity-risk/borrow-interest-rate">Borrow Interest Rate</a></p>
<table>
<thead>
<tr>
<th style="text-align: left;">USDT</th>
<th style="text-align: right;"><code class="katex-inline">U_{optimal}</code></th>
<th style="text-align: right;"><code class="katex-inline">U_{base}</code></th>
<th style="text-align: right;"><code class="katex-inline">Slope1</code></th>
<th style="text-align: right;"><code class="katex-inline">Slope2</code></th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">Variable</td>
<td style="text-align: right;">90%</td>
<td style="text-align: right;">0%</td>
<td style="text-align: right;">4%</td>
<td style="text-align: right;">60%</td>
</tr>
<tr>
<td style="text-align: left;">Stable</td>
<td style="text-align: right;">90%</td>
<td style="text-align: right;">3.5%</td>
<td style="text-align: right;">2%</td>
<td style="text-align: right;">60%</td>
</tr>
</tbody>
</table>
<h3>风险参数</h3>
<p>Risk Parameters</p>
<p>参考 <a href="https://docs.aave.com/risk/asset-risk/risk-parameters#risk-parameters-analysis">Risk Parameters</a></p>
<table>
<thead>
<tr>
<th style="text-align: left;">Name</th>
<th style="text-align: left;">Symbol</th>
<th style="text-align: right;">Collateral</th>
<th style="text-align: right;">Loan To Value</th>
<th style="text-align: right;">Liquidation Threshold</th>
<th style="text-align: right;">Liquidation Bonus</th>
<th style="text-align: right;">Reserve Factor</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">DAI</td>
<td style="text-align: left;">DAI</td>
<td style="text-align: right;">yes</td>
<td style="text-align: right;">75%</td>
<td style="text-align: right;">80%</td>
<td style="text-align: right;">5%</td>
<td style="text-align: right;">10%</td>
</tr>
<tr>
<td style="text-align: left;">Ethereum</td>
<td style="text-align: left;">ETH</td>
<td style="text-align: right;">yes</td>
<td style="text-align: right;">82.5%</td>
<td style="text-align: right;">85%</td>
<td style="text-align: right;">5%</td>
<td style="text-align: right;">10%</td>
</tr>
</tbody>
</table>
<p>实际用的是万分比</p>
<h4>抵押率</h4>
<p>Loan to Value 抵押率，表示价值 1 ETH 的抵押物，能借出价值多少 ETH 的资产</p>
<p>最大抵押率，是用户抵押的各类资产的抵押率的加权平均值</p>
<pre><code class="language-katex">Max LTV = \frac{ \sum{{Collateral}_i \ in \ ETH \ \times \ LTV_i}}{Total \ Collateral \ in \ ETH}</code></pre>
<p>比如 用户存入价值为 1 ETH 的 DAI，和 1 ETH 的 Ethereum，那么</p>
<pre><code class="language-katex">Max LTV = \frac{1 \times 0.75 + 1 \times 0.825}{1+1} = 0.7875</code></pre>
<h4>清算阈值</h4>
<p>Liquidation Threshold</p>
<pre><code class="language-katex">LiquidationThreshold = \frac{\sum{{Collateral}_i \ in \ ETH \ \times \ {Liquidation \ Threshold}_i}}{Total \ Collateral \ in \ ETH}</code></pre>
<p>上面的例子为</p>
<pre><code class="language-katex">LiquidationThreshold = \frac{1 \times 0.8 + 1 \times 0.85}{1 + 1} = 0.825</code></pre>
<p>Loan-To-Value 与 Liquidation Threshold 之间的窗口，是借贷者的安全垫</p>
<h4>清算奖励</h4>
<p>Liquidation Bonus</p>
<p>抵押物的拍卖折扣，作为清算者的奖励</p>
<h4>健康度</h4>
<p>Health Factor</p>
<pre><code class="language-katex">Hf = \frac{\sum{{Collateral}_i \ in \ ETH \ \times \ {Liquidation \ Threshold}_i}}{Total \ Borrows \ in \ ETH}</code></pre>
<p><code class="katex-inline">Hf < 1</code> 说明这个用户资不抵债，应该清算 (代码中，<code class="katex-inline">Hf</code> 单位为 ether)</p>
<p>假设用户抵押此前存入的2 ETH 资产，按最大抵押率借出价值 <code class="katex-inline">0.7875 \times 2 = 1.575</code> ETH 的债务，此时</p>
<pre><code class="language-katex">Hf = \frac{1 \times 0.8 + 1 \times 0.85}{1.575} = 1.0476</code></pre>
<p>相关代码如下</p>
<pre><code class="language-js">  /**
   * @dev Calculates the user data across the reserves.
   * this includes the total liquidity/collateral/borrow balances in ETH,
   * the average Loan To Value, the average Liquidation Ratio, and the Health factor.
   **/
  function calculateUserAccountData(
    address user,
    mapping(address =&gt; DataTypes.ReserveData) storage reservesData,
    DataTypes.UserConfigurationMap memory userConfig,
    mapping(uint256 =&gt; address) storage reserves,
    uint256 reservesCount,
    address oracle
  ) internal view returns (uint256, uint256, uint256, uint256, uint256){

    CalculateUserAccountDataVars memory vars;

    for (vars.i = 0; vars.i &lt; reservesCount; vars.i++) {
      if (!userConfig.isUsingAsCollateralOrBorrowing(vars.i)) {
        continue;
      }

      vars.currentReserveAddress = reserves[vars.i];
      DataTypes.ReserveData storage currentReserve = reservesData[vars.currentReserveAddress];

      (vars.ltv, vars.liquidationThreshold, , vars.decimals, ) = currentReserve
        .configuration
        .getParams();

      vars.tokenUnit = 10**vars.decimals;
      vars.reserveUnitPrice = IPriceOracleGetter(oracle).getAssetPrice(vars.currentReserveAddress);

      if (vars.liquidationThreshold != 0 &amp;&amp; userConfig.isUsingAsCollateral(vars.i)) {
        vars.compoundedLiquidityBalance = IERC20(currentReserve.aTokenAddress).balanceOf(user);

        uint256 liquidityBalanceETH =
          vars.reserveUnitPrice.mul(vars.compoundedLiquidityBalance).div(vars.tokenUnit);

        vars.totalCollateralInETH = vars.totalCollateralInETH.add(liquidityBalanceETH);

        vars.avgLtv = vars.avgLtv.add(liquidityBalanceETH.mul(vars.ltv));
        vars.avgLiquidationThreshold = vars.avgLiquidationThreshold.add(
          liquidityBalanceETH.mul(vars.liquidationThreshold)
        );
      }

      if (userConfig.isBorrowing(vars.i)) {
        vars.compoundedBorrowBalance = IERC20(currentReserve.stableDebtTokenAddress).balanceOf(
          user
        );
        vars.compoundedBorrowBalance = vars.compoundedBorrowBalance.add(
          IERC20(currentReserve.variableDebtTokenAddress).balanceOf(user)
        );

        vars.totalDebtInETH = vars.totalDebtInETH.add(
          vars.reserveUnitPrice.mul(vars.compoundedBorrowBalance).div(vars.tokenUnit)
        );
      }
    }

    vars.avgLtv = vars.totalCollateralInETH &gt; 0 ? vars.avgLtv.div(vars.totalCollateralInETH) : 0;
    vars.avgLiquidationThreshold = vars.totalCollateralInETH &gt; 0
      ? vars.avgLiquidationThreshold.div(vars.totalCollateralInETH)
      : 0;

    vars.healthFactor = calculateHealthFactorFromBalances(
      vars.totalCollateralInETH,
      vars.totalDebtInETH,
      vars.avgLiquidationThreshold
    );
    return (
      vars.totalCollateralInETH,
      vars.totalDebtInETH,
      vars.avgLtv,
      vars.avgLiquidationThreshold,
      vars.healthFactor
    );
  }</code></pre>
<h4>清算</h4>
<p>继续上面的例子：用户抵押价值 2 ETH 的资产，借出 1.575 ETH 的债务，<code class="katex-inline">Hf</code> 为 1.0476</p>
<p>经过一段时间后，市场可能出现如下清算场景</p>
<p>场景一：用户借出的债务从价值为 1.575 ETH，上涨为 2 ETH，此时</p>
<pre><code class="language-katex">Hf = \frac{1 \times 0.8 + 1 \times 0.85}{2} = 0.825</code></pre>
<p>场景二：用户抵押的资产 DAI，从价值为 1 ETH，下跌为 0.8 ETH，此时</p>
<pre><code class="language-katex">Hf = \frac{0.8 \times 0.8 + 1 \times 0.85}{1.575} = 0.946</code></pre>
<p>用户可能如下考虑：</p>
<p>假设，用户看多自己借出的债务，比如用户认为债务价值会继续上涨到 3 ETH，此时可以不做操作，任由清算</p>
<p>相反，用户看多自己抵押的资产，而认为债务升值无望，那么如果资产被低位清算，将得不偿失；此时用户可以追加抵押或偿还债务，避免清算</p>
<p>假设用户未及时操作，套利者先行一步触发清算，相关代码如下</p>
<pre><code class="language-js">  /**
   * @return collateralAmount: The maximum amount that is possible to liquidate given all the liquidation constraints
   *                           (user balance, close factor)
   *         debtAmountNeeded: The amount to repay with the liquidation
   **/
  function _calculateAvailableCollateralToLiquidate(
    DataTypes.ReserveData storage collateralReserve,
    DataTypes.ReserveData storage debtReserve,
    address collateralAsset,
    address debtAsset,
    uint256 debtToCover,
    uint256 userCollateralBalance
  ) internal view returns (uint256, uint256) {
    uint256 collateralAmount = 0;
    uint256 debtAmountNeeded = 0;
    IPriceOracleGetter oracle = IPriceOracleGetter(_addressesProvider.getPriceOracle());

    AvailableCollateralToLiquidateLocalVars memory vars;

    vars.collateralPrice = oracle.getAssetPrice(collateralAsset);
    vars.debtAssetPrice = oracle.getAssetPrice(debtAsset);

    (, , vars.liquidationBonus, vars.collateralDecimals, ) = collateralReserve
      .configuration
      .getParams();
    vars.debtAssetDecimals = debtReserve.configuration.getDecimals();

    // This is the maximum possible amount of the selected collateral that can be liquidated, given the
    // max amount of liquidatable debt
    vars.maxAmountCollateralToLiquidate = vars
      .debtAssetPrice
      .mul(debtToCover)
      .mul(10**vars.collateralDecimals)
      .percentMul(vars.liquidationBonus)
      .div(vars.collateralPrice.mul(10**vars.debtAssetDecimals));

    if (vars.maxAmountCollateralToLiquidate &gt; userCollateralBalance) {
      collateralAmount = userCollateralBalance;
      debtAmountNeeded = vars
        .collateralPrice
        .mul(collateralAmount)
        .mul(10**vars.debtAssetDecimals)
        .div(vars.debtAssetPrice.mul(10**vars.collateralDecimals))
        .percentDiv(vars.liquidationBonus);
    } else {
      collateralAmount = vars.maxAmountCollateralToLiquidate;
      debtAmountNeeded = debtToCover;
    }
    return (collateralAmount, debtAmountNeeded);
  }</code></pre>
<p>注意 <code>maxAmountCollateralToLiquidate</code>，表示可以被清算的最大的抵押资产的数量</p>
<p>它通过计算清算债务的价值，除以抵押资产的单位价格得到</p>
<p>由于清算者得到的是抵押资产，而抵押资产本身面临着市场波动风险，为了鼓励清算以降低系统风险，这里会凭空乘以 <code>liquidationBonus</code></p>
<p>比如清算的抵押资产为 DAI，根据上面链接的数据，该资产当前的 <code>liquidationBonus</code> 为 10500</p>
<p>即清算者支付 <code>debtAmountNeeded</code> 的债务，可以多得到 5% 的 aDAI（或 DAI）</p>
<p>实际清算时，需要考虑资产的阈值与奖励各有不同；而且 <code class="katex-inline">Hf</code> 是整体概念，而清算需要指定某个资产和某个债务；比如用户抵押 A 和 B 资产，借出 C 和 D 债务，清算时可以有 4 种选择</p>
<p>清算参数，与清算资产的多样化，使得清算策略复杂起来～</p>
<p>// 别跳清算套利的坑</p>
]]></content:encoded>
					
					<wfw:commentRss>https://godorz.info/2021/10/aave-v2/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
	</channel>
</rss>
