2025-09-24 12:12:12
为了行文方便,这篇文章不使用 “王垠老师” 这样的尊称,直接称呼名字 “王垠”。
大约 4 个月前,我开始报名学习王垠的 计算机科学视频班(基础班),经过 1 个月的学习后毕业,大概用了 120 个小时的学习时长。“120 个小时” 这个数字是经过认真估算的,包含了观看视频的时间和做练习题的时间。因为视频课的学习节奏由自己把控,毕业速度因人而异。
如果我说学了 1 个月,也许有人很不在乎,1 个月的时间能学到多少知识呢?世界上没有速成班,王垠也不可以。1 个月的时间确实不可能学到各种全面的知识,时间上也不允许。但是 1 个月的时间能不能帮我把过往的编程技能梳理一遍,让我对计算机科学有更加体系化的认识?我在工作中可能见过各种迷雾,王垠的课程能不能让我拨云见日,看清楚很多东西?
学习课程就像是去西天取经,顺利毕业相当于取到了真经。但是学习课程的过程只是老师帮你理解经书内容而已,取到真经不代表你已经对经书中的内容融会贯通,还需要在日后多加修炼、理解透彻才行。想必有的同学拿到经书(毕业)后再也不会打开看一眼,于是认识不到课程真正的价值含量。
现在课程毕业后,经过 3 个月时间的沉淀,我想分享一下 “从课程中能学到什么” 这个话题。没有在毕业当天做总结,是因为怕有点浮燥总结不全,或者掺杂太多个人经历。3 个月的时间其实也不够,我没有太多时间复习,课程也远远没来得及发挥出应有的成果,但是现在做总结并不妨碍以后对课程内容有进一步的感悟。
王垠曾经有一篇文章《爱因斯坦谈教育》,里面提到爱因斯坦说, “被传授的知识应该被当成宝贵的礼物”。我在学完课程后,对这句话有了切实的理解。
王垠并不认识我,但我早就认识王垠;我以前不是王垠的学生,但王垠早就是我的老师了。我关注王垠的博客多年,已经从他的博客文章中受益很多。而这次系统学习了视频课的课程,像是打开了新世界的大门。这并不夸张,我可以负责任地站在学习过课程的立场上讲,王垠没有吹牛,他的课程真的有他说的那么好。
你可以不赞同王垠的观点,也可以不喜欢王垠的人生态度,但是不可能怀疑王垠在计算机科学,尤其是编程语言领域的研究水平和造诣,也完全不需要担心王垠这样对自己和世界都如此较真的人,会拿一些没有含量的课程出来忽悠人。
因为课程内容是保密的,我不会透露课程具体的学习内容,只基于公开的招生说明中的内容进行解释。
对于计算机初学者来说,从课程中能学到的最直接的知识,就是函数、链表、二叉树等基础的编程概念,涵盖了大学本科整个阶段的学习内容。第 6 课关于解释器的部分,属于课程的 “最终成果”,对应一些美国顶尖大学,本科高年级阶段至硕士低年级阶段的学习内容。
为什么王垠在招生说明里的描述是 “大学博士阶段才可能学到的内容” 呢?因为中国的大学没有编程语言专业,本科课程没有教解释器的,即使到了研究生阶段,lambda 演算也属于选修课,博士早期才会接触到解释器的实现是正常现象。所以王垠的描述真的没有夸大。
解释器这种内容在中国的教育体系里本来就很稀有,比较高级和精练的教程更是少见。举例来说,当你学完了王垠的课,然后去 bilibili 上搜一些解释器相关的教程,你就会明白这些公开教程里的解释器有多么差劲,不但一眼就能看出它们缺什么要素、存在什么问题,还知道如何改进、如何用最简洁的代码写出最可靠的实现。
为什么解释器这个东西重要呢?举个例子,以太坊的虚拟机(EVM)就是一个解释器,只不过 EVM 并不是在对编程语言做解释,而是在对以太坊的操作码(Opcodes)做解释,每个操作码都对应在栈结构上的一个动作。所以学过了解释器之后,对 EVM 的原理会有不一样的理解。
对于有计算机经验的学生来说,从课程上可以学到的,就不只是表面上的知识了。比如,课程只用到非常少的编程要素,就表达了第 1 课到第 6 课的全部内容,如果王垠不是对计算机理论有非常深刻的理解,不可能做到这种地步的深入简出。
从学习者的角度,一方面可以思考为什么课程内容能如此精致,组织这些课程内容的思路是什么,这种高度抽象的思维背后,需要怎样的功底,自己距离写出这样的课程,能力上的差距还有多大。另一方面,由于课程内容自成一体,学习者完全有可能做到自己复刻整个课程内容,就像是手里的一个精致的玩物,可以随时拿出来复习把玩。
基础班的知识像是非常高级的原材料,从基础班毕业就意味着拿到了这些原材料。但原材料需要经过反复打磨、锤炼、加工,才能变成更加实际可用的装备。所以猜测有的同学学完之后感觉什么都没学到,而有的同学觉得如获至宝,能够反复加以利用并产生许多价值,大概就是这个原因吧。
我最近忽然意识到,学完课程以后,学习其他技术好像变简单了。因为体验了课程中层层递进的教学方式,我自学其他东西也会按照这个思路来,一步一步学,自然而然就学会了.
理解 CPS、基于 continuation 原理实现协程调度,学习 Solana 合约、比特币脚本、比特币 Runes、DeFi,等等。我有点说不上这是怎么回事,明明课程里只教了一些计算机基础,链表、二叉树什么的,竟然有这样的威力。
也许从课程里学到的不只是知识,还有很好的学习方法。难怪王垠把课程内容称为 “计算机科学的精华”。
虽然我有多年在区块链行业实际的工作经验,但是我却越来越搞不懂,区块链技术到底是什么。
我曾经大量研究区块链共识的原理,为什么关注的不是其他技术原理,比如加密学、分布式网络、储存系统等内容?因为只有共识是区块链特有的,其他都是普通的工程上的技术,包括智能合约的实现也是,而且智能合约还不是区块链必需,可以有也可以没有。至于 Layer 2 之类,也完全是工程上的尝试,哪有什么可以抽象的理论依据,挑战期、赎回期等都是业务定义的逻辑。我可以剧透一下,王垠的计算机课程,还涉及到了一部分区块链最本质的技术原理,是不是难以想象?
我发现,与不成体系的区块链工程技术相比,拥有基础而扎实的计算机功底更重要,尤其是高度抽象的思维方式,能够脱离语法但理解编程语言本质的学习方式,值得反复琢磨和理解。
我曾经以为,只要好好钻研区块链技术,就可以逐步提高自己的技术能力,就可以深入研究某些区块链原理,深入再深入,水平上自然而然超越所谓的计算机基础班,这也是我前几年没有报名学习课程的原因。然而事实上不是那样,经过几年切实的工作,我越发认识到课程内容的重要性。所以现在学习了课程,并且学到了很多。
学完课程以后,我其实一度怀疑自己,难道这几年折腾区块链技术都是没有意义的吗?为什么学了一个零基础入门性质的计算机课,反而感觉学到了能 “改变人生” 的东西?
一直没想通这个问题,直到后来,偶然看到王垠讲述课程设计的文章《计算机科学课程》,尤其是看到 “苏格拉底方法” 段落的时候,恍然大悟,原来我的很多知识早已散落在我的头脑中,只是这门课程帮我把知识都 “生产” 了下来。我现在拥有的知识,离不开经年累月工作和学习的 “怀胎”,也离不开这门计算机课程在恰当时候的 “助产”。
苏格拉底承认他自己本来没有知识,而他又要教授别人知识。这个矛盾,他是这样解决的:这些知识并不是由他灌输给人的,而是人们原来已经具有的;人们已在心上怀了“胎”,不过自己还不知道,苏格拉底像一个“助产婆”,帮助别人产生知识。
这就好比,在一个陌生的城市里,你没有地图,搞不清方向,但是也可以四处游荡,有时候能走的很远。而王垠的计算机课程,像是一张完整的地图,你有了地图,各个方向和岔路口都清清楚楚,但是你未必出发,也未必耗费力气去探索远方。
我的情况相当于,已经在城市里走过了很多路,偶然有一天,拿到了整个城市的地图,幡然醒悟,明白了自己所处的位置,知道了自己原先走过哪些路。这是课程对我帮助很直接的地方。
前几天面试的时候,面试官出了一道算法题,我拿到题目后,下意识说 “这看起来不是一道编程题,而是一些数字游戏”。我猜面试官听到我的话后,内心是充满鄙夷的,他也许会想,“这怎么不是编程题?”
我事后也惊讶,当时为什么会那样评价面试官出的题目,那是一种下意识的感受。后来想明白,因为你一旦上过王垠的课,就会知道真正的 “编程题” 是什么。LeetCode 上那些算法题,“编程” 的含量有多少呢?学完后课程后,你可以轻易看穿那些低水平面试官的把戏。
换个角度想,每一节课程之后,都有大量练习题需要做。如果把那些练习题,看作面试题呢?你让那些面试官来做一道试试?那可是王垠设计出的题目啊!而每一个顺利从课程毕业的同学,都必须自己做出那些题目。
王垠在微博上评论 AI 编程的时候提到,现在的 AI 很厉害,但 AI 在不经过王垠本人调教的情况下,无法写出 “王垠级别” 的代码。什么是 “王垠级别” 的代码呢?上过课就知道了。
我在做练习题的时候,被助教提醒最多次的问题,就是 “代码复杂”,有时候是写法上的复杂,有时候是复杂度上的复杂,但是每一次把代码写到符合课程标准之后,又不禁感叹原来代码可以如此精巧。我有多年的编程经验,让代码运行出练习题的结果并不难,但是把代码写的足够漂亮却不容易。“代码能运行” 和 “把代码写对” 之间,差距非常大。
有一个神奇的现象是,同一道题目,无论反复做多少次,比如做过一次后,放一个月再做一次,之前的记忆已经没有了,但是最终写出来的代码,和之前一模一样。这肯定不是偶然,因为在课程的要求下,你不得不用最简单的思路,写出最精炼的代码,而且还有代码风格和格式方面的约束,自然就会把答案的写法框定在某种形式上。
这不是一件坏事,并不是说要课程要求你教条式地背诵答案,而是在用固定的标准动作训练你的头脑,就想运动员一样,动作要标准规范,才能有好的训练效果。经过这样的训练,在日后写代码的时候,自然而然也会维持同样的动作,写出来的代码会规范和简洁。
也许有人看到招生说明会怀疑,一节选修课真能让人学会一种新的编程语言吗?我想提醒的是,不要忘了给你讲课的人是谁,是曾经写出了《如何掌握所有的程序语言》文章的作者,是真正的编程语言专家。
设想一下,假如你不想学王垠的课,但是又想掌握课程中的知识,有哪些渠道?
你花同样的钱,是没有其他地方可买的。世界上还有其他华人,能有王垠的学识背景,并且在经过几年的教学试验后,整理出如此精品的课程吗?这种水平的课程,有市无价!
你也可以自己去求学,先掌握流利的英语,然后考个美国顶尖大学的硕士,经过几年的留学生涯,不但花很多钱,还要付出许多时间、精力和努力,还需要一些天赋和运气,才能学到与王垠课程同等水平的知识。你付出的代价,远远不是只花钱就够的。
那么现在,你还觉得王垠的课程贵吗?
我刚才对王垠课程的描述是 “这种水平的课程”,那到底是什么水平呢?我可以举个具体的例子。
课程的第 1 课讲函数,对吧,所有程序员都知道函数是什么,即使不是程序员,初中上数学课也知道函数。
学习课程以前,函数是什么?函数是编程语言的语法之一,作用是把很多行代码包裹起来,方便以后重复调用。面向对象里面叫 “封装”。函数就是个特别基础的概念。
学习课程以后,函数是什么?函数可以是 “计算” 的基本元素,函数可以作为计算的输入,也可以作为计算的输出,一个计算的输出可以作为另一个计算的输入,输出的函数可以被另外的函数调用……
我这么说你肯定没看明白。
换个角度解释,王垠的导师 Daniel P. Friedman,有一本出版的书《The Little Learner》,Guy L. Steele Jr. 在给这本书的序言中写到,Friedman 在这本书里用高阶函数(higher-order functions)的 4 种不同用途,表达了机器学习(machine learning)的核心原理。
你是不是不相信,函数可以表达深度学习的原理?那就去了解一下 lambda calculus,一种和图灵机同等地位的形式化系统,可以表达的是整个计算机体系,而不只是深度学习。
这才是 “函数”。第 1 课学的函数,是这个函数。
要是单拿第 1 课的内容,或者某一课的内容出来,其实并没有那么神秘,很多地方都可以学到。王垠的课程,通过循序渐进的引导让你把这些知识学会,只是其中一方面,另一个更重要的方面在于,从第 1 课到第 6 课的内容,是非常连贯、层层递进、成体系、系统化的。
我们平时掌握的知识点,就像一个个独立的音符,大多数人都可以轻易地弹奏出某个音,但是只有把这些音符编排成曲,才能形成美妙的音乐。伟大的音乐家之所以伟大,并不是因为他们发明了新的音符,而是因为他们能把已有的音符编排成动人的旋律。
单个音符拿出来,我们很难记住,或者容易遗忘。但是一段完整的旋律,会久久盘旋在你的头脑中。即使你忘记了其中的一两个音,也可以根据整首曲子的旋律推测出来,甚至可以突发奇想,自己对旋律做一些改进。这就是系统学习知识的意义。
王垠的课程,像是某种核心(因为基础),只要掌握了这些核心内容,你就始终算是会编程、懂计算机科学的人。
这里说的基础,不是 Hello World 那种基础,而是某种结构化的基础。这些基础知识,会帮你理解 “计算” 的本质。这些核心的基础知识,才是日后真正支撑你前进、给你力量的来源。无论上层的表达形式如何变化,本质都不会变。
我以前经常担心,会不会因为做了某些工作,导致自己偏离原先的技术道路?做一些运维相关的工作,是不是就偏离了自己开发者的初心?原本做 Go 语言开发,工作需要写一些 node.js 代码,是不是就偏离后端开发的职业路径?等等。
王垠的课程内容,就属于有 “定心丸” 功效的那种。课程教给你的,是如何搭建一套自己的 “计算模型”,而不是告诉你某些已经存在的项目具体是怎么回事。所以不要纠结于课程内容是不是全面、是不是高级,因为课程本身是讲心法的,不是讲招式的。至于具体的招式,你得自己学、自己练。
学习课程以前,我在挑选工作的时候,能不能从工作中学到东西、工作有没有成长空间、有没有发展前景,都是重点需要考虑的。学完课程以后,我在考虑的,是如何根据已经掌握的知识和技能,给团队带来帮助、给公司创造价值。心态完全不一样了。因为我不再需要从工作中学到什么,我已经知道从哪里可以学到真正的技术。
当然,这事我觉得,也要看悟性。课程教归教,能收获多少,还得靠自己。
这一小节单独成文:《王垠的课能帮助程序员抵抗 AI 的冲击吗?》
2025-09-20 18:47:37
最近一段时间的工作,我几乎所有的代码,都是 AI 写的,我不再需要自己动手写代码,哪怕只是一两行代码的变动。
我只需要告诉 AI,我需要改哪里,期望的效果是什么,AI 完成的效果就很好,而且 AI 的效率很高,也不怕累,AI 技术的发展,真正实现了解放我们双手的愿景。
这不由得引起一个思考,AI 会取代程序员吗?王垠的课程,能够帮助程序员抵抗这种来自 AI 的冲击吗?
如果你用过 AI 写代码,就会明白目前的 AI,还不可能真的取代程序员,因为很多事情做不了,很多事情会做错,需要人为判断。
但即使是在短短半年前,把项目完全托管给 AI 来开发,都是不敢想的事情。半年前,我在工作中还是手动复制 AI 写的代码的模式,因为不敢让 AI 直接动项目。但是现在,AI 的 Agent 模式已经真的可以投入使用。我印象里 AI 的 Agent 模式也就今年才出现。
而且比起两年前,现在 AI 的发展速度更是非常可怕。两年前还是 GPT-3.5 的时代,不能识别图片,文本处理也存在很多幻觉。经过两年时间的发展,现在 GPT-5 的能力已经非常厉害。
所以 AI 的水平很高,虽然现在还没有特别高,但是真正让人害怕的,是 AI 的进化速度。
我自己的主观感受上,还是 GPT-5 模型最靠谱。
因为我有正事要干,没有那么多时间去试错,Claude Sonnet 4 给人的感觉就是手过于勤快,方案还没定,代码写出一堆,删都删不过来。他自己给出 A、B、C 三种方案,结果在描述方案的过程中,就把三种方案的代码全写了。相比之下,GTP-5 就好一点,会先问你用哪种方案,然后再动手。Gemini 2.5 Pro 没太用过。
我用的编辑器是 VS Code,装了 GitHub Copolit 和 ChatGPT Codex 两个插件,这两个插件都选择 GPT-5 模型。Codex 用来执行复杂任务,Copolit 做轻量级的改动。
所以我对于 AI 编程的感受主要来源于这样的使用环境。如果你用了其他的模型,或者其他的编辑器、工具,可能会跟我的感受有出入。
回到正题,我们的话题是,王垠的课,能不能抵抗这种来自 AI 的冲击?
首先是 AI 到底冲击了哪些程序员。最大的冲击是对于 3 年以下工作经验、对业务场景和技术选型都没有判断力、工作内容以领导安排为主、自己动手实现代码为结果的程序员群体。
AI 最擅长的,就是在业务场景清晰、工作目标清晰的情况下,完成代码。所以以前在公司里的小组长,有花费口舌跟新人讲清楚需求的功夫,完全可以跟 AI 描述清楚需求、AI 就把活干了。而且 AI 态度比人好、动手能力比人强、写起代码来比专家都专业。
目前的 AI 还不能取代的是在复杂业务场景下、需要对技术选型、技术方案做决策的程序员。不过这些程序员本身已经不太是基础的技术岗位,通常已经不怎么写代码。
所以进一步明确,我们在讨论的话题是,对于需要动手写代码的程序员来说,王垠的课,能不能提高这一类程序员的竞争力,抵抗至少是延缓 AI 的冲击,比其他程序员晚一步被淘汰?
我按照之前一天一道题目的计划,第三轮做练习题,最近刚做完链表相关的部分。
(题外话,想想在面试过程中,凡是让你现场写代码、做题的公司、面试官,是不是都挺扯的。不信你随便拿一道王垠出的练习题,给那些人做,你看他们能不能做出来。所以建议大家,凡是面试流程中需要做题的公司,无论面试是否通过,一律马上拒绝。)
我在 4 年前就自己写过 链表反转 之类,但是用了非常笨拙的写法,一个节点的上一个节点指向哪里、下一个节点指向哪里什么的,来对链表进行各种操作。
王垠课程里的链表,利用纯函数式的写法,以及递归的思想,代码非常简洁优雅,完全不需要考虑上一个节点、下一个节点这种东西。即使已经是第三次面对这个题目,因为已经忘了之前是怎么做的,我也很犹豫,链表反转是能用一两行代码实现的吗?终于在第三次做出题目后,才明白真的可以简单到这种程度。
所以我想说的是,王垠的课程,思想大于形式,真正能教会你的,不只有表面的知识那么简单。
同样的链表反转,初级程序员写出来是一个样,高级程序员写出来是另一个样,真的会不一样。也许表面上代码都能运行,都能达到目的。但是当面对更复杂的系统设计问题,是不是也应该尽可能做到简洁、优雅、清晰、可靠?
当你见识过最清晰的反转链表应该怎么实现,你就知道,噢,链表反转有着各种各样的写法,复杂的写法往往存在这样那样的问题,事实上问题的解法可以更优雅。
当你面对一些复杂的功能需要实现,你可能会用同样的思路去思考,是不是有更加简洁优雅的方式,来干这个事?
你只有见识过更好的东西,才能明白,什么东西更好。
AI 可以帮你干活,但是不可能代替你水平高。
当你没有见过更好的东西,你可能不知道什么是好的,想象不出什么是好的。然后你去告诉 AI,你帮我写一段很好的代码出来,AI 写了三个版本,你都不满意,最后 AI 问你,什么是 “很好的代码”?你自己也不知道。
就像我在《一个集成 Geth 和 CometBFT 的兼容层》中提到的观点,AI 可以替你干活,但是 AI 不可能代替你懂。
你可能想问,AI 帮我把活干了就行,我为什么非要懂?我不在乎代码的好坏,事实上也没人在乎,老板不在乎,客户也不在乎。只有一些自以为是高人一等但是没有实权的老员工、小组长在乎。
这其实正是我想说的,王垠的课,不局限于代码,而在于思想。
说实话,王垠课程中对于代码形式上的规则,几句话就可以说清楚。而且大部分王垠在之前的文章《编程的智慧》中都写过。(我发现现在文章被删掉了,看起来王垠删掉了很多有价值的文章)。
真正重要的不是明面上的规则,而在于课程是如何对知识化繁为简的,为什么这样是好的,那样是坏的。
同样的思想,可以迁移到其他问题上,什么是好的,什么是坏的。
你可以不在乎代码,但是始终要面对数据结构、算法、系统架构、复杂度、性能等问题。除非你真的什么都不需要面对。
AI 能解决这些问题吗?能,但是你得给 AI 说明白需求,然后看明白 AI 给你的方案,最后你来决定选择哪一种。如果你看不懂 AI 在跟你说什么,那你就不能控制 AI 了。
还是那句话,AI 不可以代替你懂。
我一直在说 “AI 不可能代替你懂”,这是我自己使用 AI 编程的体会。
王垠在以前的文章《人工智能的局限性》,包括最近在微博上,都描述了他对于 AI 的核心观点,那就是 “AI 没有读心术”。这个和我们日常使用 AI 编程的体会是一致的,你必须非常清晰告诉 AI 你想要什么,AI 才会给你什么。如果你的描述是模棱两可的,你忽悠 AI,AI 也会敷衍你。你说不清楚,AI 就做不明白。
王垠的课能帮助程序员抵抗 AI 的冲击吗?也许可以吧……假如不可以,那该怎么办?
2025-09-06 00:30:00
在对 Arc 项目 进行分析的过程中,发现 Arc 干了一件很有意思的事情,先是自己开发了 Rust 版本的 Tendermint 共识 malachite,接着开发了一个对接 Reth 和 malachite 的兼容层 malaketh-layered,也就是说,Arc 这条链的架构是这样:
Reth -> malaketh-layered -> malachite
最终形成了一条完全以太坊等价的 PBFT 链。
那么有没有类似架构的链,直接把 Geth 和 CometBFT 给结合起来呢。是有的,Berachain 开发了一个beacon-kit,干的就是这样的事情,Berachain 主网本身就是这种架构启动的。
但是 beacon-kit 有一个问题,就是代码过度 “复杂”,不但自己设计了 slot 的概念,还把 Berachain 的一些经济模型的设计、LST 质押之类的东西都放到了 beacon-kit 中。所以虽然 beacon-kit 在工程上是一个 Geth+CometBFT 可行的实践,但是它本身并不是工具性质的立场在做,夹带了不少私货。
因此我觉得需要一个通用的、工具性质的兼容层项目,目前命名为 EthBFT。这个项目的愿景是,提供简洁、开放、最小实现、工具性质的架构,达到集成 Geth 和 CometBFT 的目的。整个区块链网络的架构会是这样:
Geth -> EthBFT -> CometBFT
EthBFT 主要干两件事情:
这里虽然用 Geth 举例,但对于其他以太坊的执行层客户端,应该也是通用的,因为以太坊的执行层和共识层客户端,本来就是互相兼容的,仅仅通过 RPC 接口通信。所以预计 EthBFT 可以兼容全部的以太坊执行层客户端。
而 EthBFT 的设计,自然不会和 Geth 或者 CometBFT 有代码层面的耦合,EthBFT 是一个独立的进程,可以单独启动,Geth 也可以单独启动,CometBFT 也可以单独启动,3 个组件之间,彼此通过 RPC 接口通信,具体的 RPC 接口地址等信息则会体现在 EthBFT 的配置文件中。
这就让 3 个组件互相之间,完全解耦了。
我之前以为区块链技术的发展会趋于追新,也会趋于去中心化,但是发现似乎不是那样。
从前两年的 Celestia 使用了 PBFT,到 Hyperliquid 改进了 PBFT 共识,再到最近 Arc 项目自己实现了 PBFT 共识,证明在高TPS的场景下,PBFT算法还非常有活力。
PoW 和 PoS 去中心化程度高,但是不能满足高 TPS 的需求,也不能达到最终一致性的要求,这些都是 PBFT 特有的优势,尤其是企业级的应用场景下,没那么在意去中心化。
我们也许会有疑问,如果不在乎去中心化,那直接用 Server 端提供服务不就行了吗,用区块链干什么。在丢失去中心化特性的前提下,至少区块链还保留有数据公开、数据变更可追溯等特点,也是一些不错的优势。
因此,PBFT 这种诞生接近 30 年的算法,将来还会继续发光发热。也因此,去搞一个 PBFT 相关的项目,不会有太大问题。
EthBFT 肯定不会受到市场的关注,因为大家只在乎一条链能不能发币,能不能套利,并不在乎你的技术架构是什么。
EthBFT 只是一个工具性质的项目。如果一个开发者,想要一条以太坊完备的链,同时又想要高 TPS,在没有 EthBFT 的情况下,需要怎么做呢。我懒得展开分析对比搭建链的方案了,总之我觉得 EthBFT 可以填补这部分的空缺,非侵入式那种。世界上缺一个这样的工具。
现在 smallyunet/EthBFT 项目已经有了基本的框架,能跑通最小版本,我把它归档为 v0.0.1 版本。能跑通的表现是 Geth 的区块高度会逐渐增加,CometBFT 也在正常出块,Geth 和 CometBFT 的区块高度保持同步。当然现在还属于非常早期的版本,开发时间有限,功能上肯定有不完善的地方,接下来还会继续改进。
我之前说 鼓吹 Cursor 的人技术能力都差,因为 AI 可以放大你的能力,但是不可能代替你懂。v0.0.1 版本的 EthBFT,全部代码都是 AI 写的,没错,但是以 EthBFT 这个项目为例,现在要干的事情非常清晰,你可以试试,在不懂以太坊和 Cosmos,甚至不懂技术的情况下,完全托管给 AI,能不能搞出一个能运行的、EthBFT 这样的项目。
如果你自己对技术的理解不清晰,或者有错误,关键是 AI 不会纠正你的错误,因为 AI 并不知道你心里想要的 “正确” 是什么。AI 会非常听话地按照你的描述写代码,如果你语焉不详,AI 写出来的代码必然会跑偏,朝着错误的方向发展,而且很多时候 AI 会自己偷偷埋坑,你以为它实现了,结果它要么没写全,放了个 TODO 在那儿,要么按照自己的理解写出一大堆不需要的代码。
所以让 AI 把代码写对,其实不是一件容易的事情,首先你自己得懂,然后你得时刻盯着它干活。AI 始终只是助手而已。
2025-08-21 19:50:22
这是一个 DeFi 系列教程,在动手实践的过程中,学习和理解 DeFi 相关的概念与原理:
闪电贷套利是我们经常听到的一个词,在实际的场景中有很多种模式。基于我们之前的 AMM 合约,就足以让我们来模拟一个简单版本的闪电贷套利。
闪电贷的核心逻辑是,利用区块链智能合约的特性,在一笔交易内,借来大额资产(放大收益),拿着大额资产去执行别的操作,干什么都行,干完事情之后,再把借来的资金+少量手续费,原封不动还回去。因为智能合约是可以 revert 的,如果套利合约在执行过程中,发现最后套利没成功,可以回滚整个交易,除了手续费,没有额外损失。
我们接下来要模拟的场景时,有两个 AMM 池子,第一个池子的价格是 2000 USDC/WETH,第二个池子的价格是 4000 USDC/WETH。面对这样的场景,可以先想一下,不用闪电贷的情况下,应该如何套利。套利合约只不过是把我们的操作自动化了。
很简单,在第一个池子化 2000 USDC 买 1 个 WETH,到第二个池子上,直接就能卖出 4000 USDC,净赚 2000 USDC。
我们会用到两个合约 FlashLender.sol 和 FlashArbBorrower.sol。这两个合约代表两个角色,其中 borrower 就是套利合约,会从 lender 那里借出一些资金。
也就是说 lender 合约,是有 借出资产功能 的:
// 借出资产require(t.transfer(receiver, amount), "transfer out");// 调用 borrower 的回调函数bytes32 magic = IFlashBorrower(receiver).onFlashLoan(token, amount, fee, data);require(magic == keccak256("IFlashBorrower.onFlashLoan"), "bad callback");// 验证在回调函数后,borrower 是否归还了本金+手续费uint256 balAfter = t.balanceOf(address(this));require(balAfter >= balBefore + fee, "not repaid");
关键就是这 3 行,先借出钱,然后回调,最后判断 borrower 是否还款。
在 borrower 的回调函数里,会写一些具体的 套利逻辑,比如从第一个池子买入 WETH,然后再卖到第二个池子:
// 从便宜的池子中买 WETHuint256 wethOut = poolCheap.swap0For1(amount);// 到贵的池子中卖 WETHuint256 usdcBack = poolExpensive.swap1For0(wethOut);// 还款给 lenderuint256 repay = amount + fee;
lender 和 borrower 是两个角色,那为什么不把这些逻辑写在一个合约里呢?如果写在一个合约里,意味着只有一个套利合约的角色,自己借钱出去、自己用钱套利,我自己都有钱了还借钱干嘛?
合约代码源文件在仓库:smallyunet/[email protected]
克隆仓库:
git clone https://github.com/smallyunet/defi-invariant-lab/git switch v0.0.4cd defi-invariant-lab
部署合约:
forge create \ --rpc-url $RPC_URL \ --private-key $PK_HEX \ --broadcast \ contracts/amm/SimpleAMM.sol:SimpleAMM \ --constructor-args $USDC_ADDR $WETH_ADDR 30
部署后的合约地址:0xd9c870Ac0a84C3244286d39d870642d218b26532
这个 AMM_B 池子我们认为是价格比较高的池子,所以按照 4000 USDC/WETH 的价格注入初始流动性:
export AMM_B=0xd9c870Ac0a84C3244286d39d870642d218b26532cast send $USDC_ADDR "approve(address,uint256)" $AMM_B \ 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff \ --rpc-url $RPC_URL --private-key $PK_HEXcast send $WETH_ADDR "approve(address,uint256)" $AMM_B \ 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff \ --rpc-url $RPC_URL --private-key $PK_HEXcast send $AMM_B "addLiquidity(uint256,uint256)" 4000000000000 1000000000000000000000 \ --rpc-url $RPC_URL --private-key $PK_HEX
部署合约:
forge create \ --rpc-url $RPC_URL \ --private-key $PK_HEX \ --broadcast \ contracts/flash/FlashLender.sol:FlashLender \ --constructor-args 5
部署后的合约是:0x3c00AB1eD5dF40f7ae8c1E4104C89445615B9D0a
验证合约:
forge verify-contract \ --chain-id 11155111 \ 0x3c00AB1eD5dF40f7ae8c1E4104C89445615B9D0a \ contracts/flash/FlashLender.sol:FlashLender \ --constructor-args $(cast abi-encode "constructor(uint16)" 5) \ --etherscan-api-key $ETHERSCAN_API_KEY
给 lender 转 20 万 USDC 作为初始资金:
export LENDER=0x3c00AB1eD5dF40f7ae8c1E4104C89445615B9D0acast send $USDC_ADDR "approve(address,uint256)" $LENDER \ 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff \ --rpc-url $RPC_URL --private-key $PK_HEXcast send $LENDER "fund(address,uint256)" $USDC_ADDR 200000000000 \ --rpc-url $RPC_URL --private-key $PK_HEX
部署合约:
forge create \ --rpc-url $RPC_URL \ --private-key $PK_HEX \ --broadcast \ contracts/flash/FlashArbBorrower.sol:FlashArbBorrower \ --constructor-args $LENDER $USDC_ADDR $WETH_ADDR $AMM_ADDR $AMM_B
部署的合约地址是:0x62363Fe02b83b804fd65FE3b862383631fEffb49
验证合约:
forge verify-contract \ --chain-id 11155111 \ 0x62363Fe02b83b804fd65FE3b862383631fEffb49 \ contracts/flash/FlashArbBorrower.sol:FlashArbBorrower \ --constructor-args $(cast abi-encode "constructor(address,address,address,address)" $LENDER $USDC_ADDR $WETH_ADDR $AMM_ADDR $AMM_B) \ --etherscan-api-key $ETHERSCAN_API_KEY
直接调用 execute 函数:
export ARB=0x62363Fe02b83b804fd65FE3b862383631fEffb49cast send $ARB "execute(uint256)" 10000000000 \ --rpc-url $RPC_URL --private-key $PK_HEX
我发起的交易哈希是:0x948edfb7eeb5ceb924c1eb39704efd952f1cd3ed435c059a548dd7ea82031f15
浏览器的交易过程比较直观,直接看浏览器的记录就好了:
可以看到在第一个池子里,用 10000 USDC 买了 4.6 个 WETH,紧接着把 WETH 卖掉,换出了 18000 USDC,净赚 8000 USDC。
borrower 的合约里有写把收益金额 emit 为事件:
uint256 profit = usdcBack - repay;if (profit > 0) { require(usdc.transfer(owner, profit), "payout fail"); emit Profit(profit);}
所以在浏览器上也能看到真的 触发了事件:
2025-08-21 11:44:18
这是一个 DeFi 系列教程,在动手实践的过程中,学习和理解 DeFi 相关的概念与原理:
我们已经有了两个 ERC-20 代币 USDC 与 WETH,有了 AMM 合约,有了 Oracle 合约。接下来利用之前的合约,尝试和理解一下借贷相关的合约逻辑。
借贷合约要注意的地方是,在计算用户能借出多少资产的逻辑中,需要用到代币的价格。这里的代币价格,来自 Oracle 的报价,而不是 AMM 合约的价格。Oracle 的报价一般基于 AMM 的价格波动,如果 Oracle 遭受攻击,借贷合约也会相应受到影响。
合约代码源文件在仓库:smallyunet/[email protected]
Oracle 使用的合约是 SimpleLending.sol,先克隆仓库:
git clone https://github.com/smallyunet/defi-invariant-lab/git switch v0.0.3cd defi-invariant-lab
部署合约:
forge create \ --rpc-url $RPC_URL \ --private-key $PK_HEX \ --broadcast \ contracts/lending/SimpleLending.sol:SimpleLending \ --constructor-args $WETH_ADDR $USDC_ADDR $ORACLE_ADDR
部署地址:0xd4bbFbCe71038b7f306319996aBbe3ed751E9A1C
验证合约:
forge verify-contract \ --chain-id 11155111 \ 0xd4bbFbCe71038b7f306319996aBbe3ed751E9A1C \ contracts/lending/SimpleLending.sol:SimpleLending \ --constructor-args $(cast abi-encode "constructor(address,address,address)" $WETH_ADDR $USDC_ADDR $ORACLE_ADDR) \ --etherscan-api-key $ETHERSCAN_API_KEY
给借贷合约挖 10 万个 USDC,作为初始可以借贷的资产:
export LEND_ADDR=0xd4bbFbCe71038b7f306319996aBbe3ed751E9A1Ccast send $USDC_ADDR "mint(address,uint256)" $LEND_ADDR 100000000000 \ --rpc-url $RPC_URL --private-key $PK_HEX
存入 1 个 WETH 作为抵押物:
cast send $WETH_ADDR "approve(address,uint256)" $LEND_ADDR \ 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff \ --rpc-url $RPC_URL --private-key $PK_HEXcast send $LEND_ADDR "deposit(uint256)" 1000000000000000000 \ --rpc-url $RPC_URL --private-key $PK_HEX
调用 borrow
函数借出 USDC,借出额度的计算是:
function borrow(uint256 amt) external { _accrue(); require(_value(coll[msg.sender]) * LTV_BPS / 10_000 >= borrows[msg.sender] + amt, "exceeds LTV"); borrows[msg.sender] += amt; totalBorrows += amt; debt.transfer(msg.sender, amt);}
我们抵押了 1 个 WETH,按照 2000 USDC/WETH 的价格,合约设定 LTV 最高 70%,也就是可以借出 2000*0.7=1400
个USDC。
来用实际交易试一下,这次借出 1400 个 USDC:
cast send $LEND_ADDR "borrow(uint256)" 1400000000 \ --rpc-url $RPC_URL --private-key $PK_HEX
查看借出 USDC 后的余额、负债、健康度:
cast call $USDC_ADDR "balanceOf(address)(uint256)" $MY_ADDR --rpc-url $RPC_URL# 799400000000 [7.99e11]cast call $LEND_ADDR "borrows(address)(uint256)" $MY_ADDR --rpc-url $RPC_URL# 1400000000 [1e9]cast call $LEND_ADDR "health(address)(uint256)" $MY_ADDR --rpc-url $RPC_URL# 1904761904285714285 [1.904e18]
这里的健康度,指是否有可能触发清算。当查询结果大于 1,则比较安全。当健康度小于 1,则可以被清算机器人、套利者清算掉。
如果想还债的话,调用 repay
函数就可以了。
现在要体验一次清算逻辑,我们之前抵押了 1 WETH,价值 2000 USDC,借出了 1400 USDC,此时 LTV=1400/2000=70%,正好是 70%,处于安全状态。
当价格下跌到 1000 USDC/WETH,此时的 LTV=1400/1000=140%,已经超过 70% 的安全值,也超过了 75% 的清算阈值。
我们修改下在预言机里的价格,让借贷合约感知到 WETH 价格下跌了(这也就是预言机的主要作用,决定了链上的报价):
cast send $ORACLE "post(uint256[])" \ "[99900000000,100000000000,100000000000,100000000000,100100000000]" \ --rpc-url $RPC_URL --private-key $PK_HEX
再查一下健康度:
cast call $LEND_ADDR "health(address)(uint256)" $MY_ADDR --rpc-url $RPC_URL# 952380952142857142 [9.523e17]
这里的健康度实际上是 0.9 1e18,已经小于 1 了,处于可以被清算的状态。
任何人都可以执行清算,执行清算成功后,可以获得 10% 的清算奖励,这就是很多人需要抢跑交易、优先执行清算的原因。10% 的清算奖励是指,假如你替抵押者还债 200 USDC,让他的仓位健康度大于 1,那么这个时候,按理你可以清算(部分清算)得到 0.2 WETH,由于 10% 的清算奖励,你实际上得到了 0.22 WETH。
我们现在执行交易还债 200 USDC:
cast send $USDC_ADDR "approve(address,uint256)" $LEND_ADDR \ 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff \ --rpc-url $RPC_URL --private-key $PK_HEXcast send $LEND_ADDR "liquidate(address,uint256)" $MY_ADDR 300000000 \ --rpc-url $RPC_URL --private-key $PK_HEX
查看执行清算后,一些数据的变化:
cast call $LEND_ADDR "borrows(address)(uint256)" $MY_ADDR --rpc-url $RPC_URL# 1200000000 [1.2e9]cast call $LEND_ADDR "coll(address)(uint256)" $MY_ADDR --rpc-url $RPC_URL# 999999999780000000 [9.999e17]cast call $WETH_ADDR "balanceOf(address)(uint256)" $MY_ADDR --rpc-url $RPC_URL# 899987136079723472734 [8.999e20]
可以看到,Defi 的借贷就是在玩这些金钱的数字游戏。
DeFi 开发的难点在于,需要理解一大堆金融相关的公式,看懂合约代码背后表达的业务含义,计算利息、负债率什么的。这个方向对金融行业从业者更友好一点。
Solidity 语言只是表达金融公式的工具,Solidity 的语法本身很简单,普通的开发人员很快就可以掌握。但是掌握 Solidity 语法,不代表能够理解金融体系,不代表能看懂金融公式。
2025-08-21 11:24:02
这是一个 DeFi 系列教程,在动手实践的过程中,学习和理解 DeFi 相关的概念与原理:
预言机(Oracle)的逻辑相对简单,基本功能是,会有链下服务定时向链上提交一些数据,比如 WETH 的价格,合约保存下数据后,就可以被其他智能合约调用,直接获取到价格信息。
那么链下服务的价格信息,从哪里来?简单处理的话,可以来自 AMM 合约的初始流动性的定价。
以下所有操作都在 Sepolia 测试网进行。这些操作步骤,其实都是一些普通的合约交互步骤。主要是在操作过程中,进一步体会和理解合约的代码功能。
合约代码源文件在仓库:smallyunet/[email protected]
Oracle 使用的合约是 MedianOracle.sol,先克隆仓库:
git clone https://github.com/smallyunet/defi-invariant-lab/git switch v0.0.2cd defi-invariant-lab
部署合约:
forge create \ --rpc-url $RPC_URL \ --private-key $PK_HEX \ --broadcast \ contracts/oracle/MedianOracle.sol:MedianOracle
部署的合约地址是 0xdE342a228A2A83b47cA4eB3D3852578837E60750
。
验证合约:
forge verify-contract \ --chain-id 11155111 \ 0xdE342a228A2A83b47cA4eB3D3852578837E60750 \ contracts/oracle/MedianOracle.sol:MedianOracle \ --etherscan-api-key $ETHERSCAN_API_KEY
调用合约的 setFeeder
函数,设定谁可以向 Oracle 提交数据:
export ORACLE_ADDR=0xdE342a228A2A83b47cA4eB3D3852578837E60750cast send $ORACLE_ADDR "setFeeder(address,bool)" \ 0x44D7A0F44e6340E666ddaE70dF6eEa9b5b17a657 true \ --rpc-url $RPC_URL --private-key $PK_HEX
然后能查询到设置结果:
cast call $ORACLE_ADDR "feeders(address)((bool))" $MY_ADDR --rpc-url $RPC_URL# (true)
发起交易:
cast send $ORACLE_ADDR "post(uint256[])" \ "[199900000000,200000000000,200000000000,200000000000,200100000000]" \ --rpc-url $RPC_URL --private-key $PK_HEX
读取价格:
cast call $ORACLE_ADDR "latest()(uint256,uint256)" --rpc-url $RPC_URL