作者:Figo, IOSG Ventures
命令式与声明式编程:范式的转变
在区块链世界中,编程范式定义了开发人员如何与技术交互以及用户如何体验结果。让我们使用一个相关的类比来分析传统命令式方法和新兴声明式方法之间的主要区别——在您最喜欢的咖啡馆订购定制咖啡。
命令式编程:遵循菜谱
想象一下,你走进一家咖啡馆,向咖啡师递上一份制作咖啡的详细步骤清单 — “研磨 20 克咖啡豆,使用 200 毫升 90°C 的水,冲泡 4 分钟,倒入陶瓷杯中。”你需要对整个过程进行细致入微的管理,确保每一步都严格遵循,才能制作出你想要的咖啡。
这就是命令式编程在区块链中的工作方式。开发人员编写智能合约来规定流程的每个步骤,从获取数据到执行交易。虽然这可以确保流程按预期进行,但它可能僵化且效率低下,尤其是在出现意外情况时。
声明式编程:定义结果
现在,想象一下你走进同一家咖啡馆,简单地说:“我想要一杯中浓咖啡,不加糖,用陶瓷杯装。”咖啡师知道你想要什么,并可以选择最佳的冲泡方式,无论是使用不同的冲泡方法还是根据可用的咖啡豆调整研磨尺寸。
这就是声明式编程的精髓。您无需指定每个步骤,只需定义所需的结果,系统就会找出实现该结果的最佳方法。这种方法更加灵活,可以根据当前条件和用户偏好进行调整。
下表重点介绍了主要区别:
为什么区块链应用程序需要意图?
理解意图
在传统的区块链设置中,您需要提供分步说明来实现特定的状态更改。但是,如果您可以指定最终目标,并让系统找出实现该目标的最佳方法,情况会怎样?这就是区块链意图背后的想法——用户声明他们想要什么,系统处理其余的事情。
例如,用户可能不会告诉区块链“以最佳价格出售 1 ETH ”,而是表明“用 1 ETH换取至少 2500 USDT ”的意图。然后,系统会找到满足这一请求的最佳方式,可能跨越多个交易所或流动资金池。
命令式区块链的挑战
命令式编程的僵化常常导致几个问题:
- 不确定的结果:用户通常在交易确认之前不知道交易的结果,这可能会带来压力和风险。
- 市场效率低下:当对特定结果的需求很高时,命令式执行可能会遇到瓶颈,例如在 DeFi 交易中。
- 安全风险:更详细的步骤意味着更多的潜在故障点,从而增加了出现漏洞和漏洞的风险。
这些问题在 DeFi 这样的环境中尤为普遍,用户面临交易失败、高滑点和 MEV(矿工可提取价值)风险等问题。
声明式区块链的兴起
像 Essential 这样的声明式区块链旨在通过关注结果而不是过程来解决这些问题。通过定义最终状态并使用意图来实现它,这些系统提供了几个优点:
- 可预测的结果:用户可以相信区块链将提供所需的结果。
- 提高安全性:步骤更少意味着出错的机会更少。
- 可扩展性:通过优化结果,声明式区块链可以处理更复杂的操作,而不需要更多的计算资源。
以意图为中心的语言
随着以意图为中心的编程在区块链领域越来越受欢迎, Pint (由 Essential 开发)和Juvix (由 Anoma 开发)等语言应运而生,它们可以帮助开发人员专注于结果而不是具体步骤。这些语言提供了一种区块链开发的新方法,优先考虑系统的理想状态,而不是实现该状态的程序指令。
在本节中,我们将深入研究 Essential 声明式区块链背后的语言Pint ,探索它的运作方式以及与我们习惯的不同之处。
Pint 是一种专门为 Essential 以意图为中心的区块链设计的约束建模语言。与传统的命令式智能合约语言不同,Pint 强调定义状态变化的期望结果,而不指定实现这些结果的具体步骤。这种重点转移使区块链应用程序的开发和执行方式更加灵活、安全和高效。
Pint 有何与众不同之处?
Pint 提供了一种处理区块链逻辑的独特方法,它专注于约束和谓词,而不是传统的执行模型。它的工作原理如下:
- 合约:在 Pint 中,合约定义了更新区块链状态的规则。与传统合约不同,合约不指定如何执行更改;相反,合约验证提议的状态更改是否符合某些标准。
- 谓词:谓词是状态改变有效所需满足的条件。它们充当过滤器,确定特定状态转换是否可以发生。
- 约束:约束是谓词的组成部分。这些布尔表达式的计算结果必须为 True,谓词才能通过。例如,约束可能要求计数器正好增加 1,以确保状态更新的一致性。
- 状态:状态变量表示区块链上的值。与传统的命令式语言不同,开发人员编写一些应用于前状态的执行来确定后状态,而 Pint 则同时提供前状态和后状态作为输入。对于给定的声明状态 foo,foo 指的是前状态,而 foo' 指的是后状态。
- 决策变量:决策变量允许求解器提供满足谓词约束所需的额外数据。例如,代币合约的转移谓词可能需要有效签名。
初探 Pint:简单的反例
为了更好地理解 Pint 的工作原理,让我们看一个简单的例子——反向合约。
贮存 {
计数器:int,
}
谓词增量 {
状态计数器:int = storage::counter; // 读取计数器的当前值
约束(计数器 == nil && 计数器' == 1)|| 计数器' == 计数器 + 1;
// 该约束确保如果未设置计数器,则从 1 开始。
// 否则,将当前值增加 1。
}
存储块:
- 存储块定义了一个将存储在区块链上的单个整数变量计数器。这是计数器当前值保存的地方。
谓词增量:
- 谓词 Increment 定义了增加计数器的逻辑。
- 线状态计数器:int = storage::counter;从存储中读取计数器的当前值。
- 约束 (counter == nil && counter' == 1) || counter' == counter + 1; 确保如果计数器尚未初始化(即,它为 nil),则它从 1 开始。否则,计数器将增加 1。
简单来说,这个合约规定,“如果计数器尚不存在,则从 1 开始。如果计数器存在,则将当前值加 1。”这个例子说明了 Pint 如何允许开发人员定义所需的结果(增加计数器),而无需指定每个步骤(检查计数器、添加计数器ETC)。
从头开始构建简单的加密货币
现在来看一个更复杂的例子?让我们一步一步地看看如何使用 Pint 从基本原理开始构建基本的加密货币。假设我们的任务是创建一个数字货币系统。我们需要什么?
步骤 1:定义核心组件
首先,我们需要确定加密货币的基本要素。从最基本的层面上讲,我们需要:
- 货币总供应量的记录:这跟踪了货币的存在数量。
- 跟踪余额的方法:我们需要知道每个账户持有多少货币。
在 Pint 中,我们可以使用存储块定义这些:
贮存 {
总供给量:int,
余额:(b256 => int),
}
- total_supply :这个整数跟踪货币的总量。
- balances :这是一个映射(或字典),将每个地址(用 b256 表示,即 256 位哈希值)与其对应的余额关联起来。
第 2 步:创建新货币
接下来,我们需要一种创建新货币的方法——也称为铸造。铸造货币时,我们希望增加总供应量并更新接收者的余额。
让我们思考一下以下步骤:
- 我们需要明确谁将获得新铸造的货币以及他们将获得多少。
- 然后,我们需要相应地更新总供应量和收件人的余额。
这种逻辑在 Pint 中表现如下:
谓词 Mint {
var接收器:b256;
变量金额:int;
状态接收器平衡 = mut 存储::balances[接收器];
状态总供应 = mut 存储::总供应;
约束总供应量'==总供应量+数量;
约束接收方余额'==接收方余额+金额;
}
分解如下:
- 决策变量:接收者和金额代表接收者的地址和要铸造的货币数量。
- 状态变量:我们引用 total_supply 和receiver_balance 的状态,声明它们是可变的(mut),这意味着它们可以被更新。
- 限制:
— 第一个约束确保总供应量按照铸造量增加。
— 第二个约束确保接收者的余额根据铸造的金额进行更新。
但是,需要注意的是,此示例已简化以便于理解。合同本身没有内置身份验证。这意味着任何人都可以铸造新币或将其从任何账户转移到另一个账户。在现实世界中,这种缺乏安全性将是一个重大问题。在后面的部分中,我们将探讨如何添加身份验证以确保只有授权用户才能铸造或转移货币,从而使系统更加安全。
步骤 3:用户之间转移货币
现在我们可以创建货币了,下一步就是让用户能够互相发送货币。为此,我们需要:
- 检查发送者是否有足够的货币进行发送。
- 从发送者的余额中扣除该金额。
- 将金额添加到收款人的余额中。
这是相应的 Pint 代码:
谓词发送 {
var 来自:b256;
var接收器:b256;
变量金额:int;
状态 from_balance = mut storage::balances[from];
状态接收器平衡 = mut 存储::balances[接收器];
约束金额 < from_balance;
约束 from_balance' == from_balance - 金额;
约束接收方余额'==接收方余额+金额;
}
分解如下:
- 决策变量:发件人、接收者和金额指定谁发送货币、谁接收货币以及发送金额。
- 状态变量:from_balance 和receiver_balance 是发送者和接收者的当前余额,是可变的。
- 限制:
— 第一个约束检查发送者是否有足够的余额来支付转账。
— 第二个限制从发送者的余额中扣除金额。
— 第三个约束将金额添加到收件人的余额中。
再次强调,这里的简单性忽略了身份验证,而身份验证在实践中对于防止未经授权的转账至关重要。添加这样的安全层将涉及更复杂的谓词,在允许交易之前验证用户的身份。
以下是完整的合约,结合了铸造和转让功能:
贮存 {
总供给量:int,
余额:(b256 => int),
}
谓词 Mint {
var接收器:b256;
变量金额:int;
状态接收器平衡 = mut 存储::balances[接收器];
状态总供应 = mut 存储::总供应;
约束总供应量'==总供应量+数量;
约束接收方余额'==接收方余额+金额;
}
谓词发送 {
var 来自:b256;
var接收器:b256;
变量金额:int;
状态 from_balance = mut storage::balances[from];
状态接收器平衡 = mut 存储::balances[接收器];
约束金额 < from_balance;
约束 from_balance' == from_balance - 金额;
约束接收方余额'==接收方余额+金额;
}
该子货币合约为基本加密货币提供了基础框架,说明了意图中心编程如何通过关注每个操作的期望结果来简化流程。
建立NFT合约
现在我们已经构建了一个基本的加密货币,让我们探索一个更复杂、更微妙的应用程序:管理 NFT(非同质化代币)。与每个单位都相同的同质化代币不同,NFT 代表独特的资产,需要更复杂的合约设计。
以下是在 Pint 中构建 NFT 系统的方法:
使用 std::lib::PredicateAddress;
使用 std::auth::@auth;
使用 std::lib::@safe_increment;
使用 std::lib::@mut_keys;
贮存 {
所有者:(int => b256),
nonce: (b256 => int),
}
接口认证{
谓词 谓词 {
// 授权谓词正在输出的地址。
// 这将授权谓词指向该集合中的谓词。
// 通过设置此地址,授权就不能与错误的谓词一起使用。
pub var addr:{ contract:b256, addr:b256 };
}
}
谓词 Mint {
var 令牌:int;
var 新所有者:b256;
状态所有者 = mut storage::owners[token];
约束所有者==nil;
约束所有者'==新所有者;
}
谓词转移 {
// 发送金额的地址。
公共变量键:b256;
// 发送金额的地址。
出版变量为:b256;
// 正在转移的令牌。
酒吧 var 令牌: int;
状态所有者 = mut storage::owners[token];
状态 nonce = mut storage::nonce[key];
约束所有者==键;
约束所有者'==至;
约束@safe_increment(nonce);
// 检查授权谓词。
var auth_addr:谓词地址;
接口 AuthI = Auth(auth_addr.contract);
谓词A = AuthI::Predicate(auth_addr.addr);
@auth(key; A::addr; auth_addr; @transfer());
}
谓词取消 {
// 取消转账或者销毁的账户。
公共变量键:b256;
状态 nonce = mut storage::nonce[key];
// 增加 nonce,以便任何待处理的转账或
// 烧毁无效。
约束@safe_increment(nonce);
// 检查授权谓词。
var auth_addr:谓词地址;
接口 AuthI = Auth(auth_addr.contract);
谓词A = AuthI::Predicate(auth_addr.addr);
@auth(key; A::addr; auth_addr; @cancel());
}
宏@transfer(){/{合同:signed::ADDRESS,地址:signed::Transfer::ADDRESS}}
宏@cancel(){ { contract:signed::ADDRESS, addr:signed::Cancel::ADDRESS }}
存储块:
- 所有者:将令牌 ID 映射到其各自的所有者。
- nonce:跟踪每个账户的 nonce,以确保交易是唯一的且不可重放。
谓词薄荷:
- 通过为新所有者分配代币 ID 来铸造新的 NFT。
- 约束确保令牌 ID 之前未分配过(owner == nil),然后将其分配给 new_owner。
谓词转移:
- 管理 NFT 从一个所有者到另一个所有者的转移。
- 它包括授权检查,以确保只有当前所有者可以转移令牌。
- @auth 宏与外部授权合约集成以验证交易。
谓词取消:
- 允许帐户通过增加随机数来取消待处理的转账或销毁,这会使任何先前签署的交易无效。
- 它还与授权系统集成,以确保只有授权用户才能取消交易。
这份 NFT 合约展示了 Pint 如何处理更复杂的现实世界应用程序,同时集成授权和交易完整性检查等基本安全功能。它展示了以意图为中心的编程如何通过专注于系统应实现的目标,将底层流程管理留给语言及其求解器,从而简化高级区块链应用程序的开发。
为什么开发人员应该关心
以 Pint 为例的以意图为中心的编程代表了区块链开发的一种新思维方式。通过抽象执行细节并专注于结果,开发人员可以创建更强大、更安全、更灵活的应用程序。这种范式转变降低了出错的可能性,简化了编码过程,并为更具创新性和更高效的解决方案打开了大门。
随着区块链技术的不断发展,掌握像 Pint 这样的语言对于想要保持领先地位并构建下一代分散式应用程序的开发人员来说至关重要。
结束语
以意图为中心的编程不仅仅是一种编写智能合约的新方法,它还是对区块链应用程序的设计和执行方式的根本性重新思考。通过关注结果而不是指令,开发人员可以创建更安全、可扩展且用户友好的应用程序,这些应用程序更适合现代区块链的动态和复杂环境。
随着以意图为中心的编程变得越来越普遍,我们可能会看到它的原理从区块链扩展到软件开发的其他领域,例如物联网、人工智能,甚至传统的网络应用程序。通过关注结果而不是程序,各行各业的开发人员可以解锁新的效率、安全性和灵活性水平。软件的未来很可能由这种范式转变来定义。
要了解有关 Pint 的更多信息并深入了解以意图为中心的编程,请查看Pint 文档以获取详细指南和示例。对于那些有兴趣在 Essential 上部署的人,请访问Essential 的官方文档,开始构建和部署您自己的以意图为中心的应用程序。
初探以意图为中心的编程 ft. Pint最初发表在 Medium 上的IOSG Ventures中,人们通过突出显示和回应这个故事来继续讨论。