programming

Plutus教程:#12 最佳实践

12. 智能合约最佳实践 以下是使用 Plutus 开发智能合约时应遵循的一些通用最佳实践。 12.1 了解智能合约的目标 确切地知道您的智能合约应该做什么并对其进行测试以确保它实现预期目标。 12.2 检查以前的类似合同是否存在 做同样事情的合同是否已经存在?如果是这样,你的智能合约有什么不同? 12.3 命名 为您的智能合约选择一个清晰而有意义的名称。保持简短,并尝试使用名称来传达合同的作用。 12.4 代码同行评审 代码审查的好处众所周知;审查的代码导致整个项目的一致性、无缺陷和针对性能优化的代码。代码知识在开发人员之间共享,减少了未来的维护时间和成本。对于智能合约,正确性绝对是至关重要的。 12.5 在测试网上测试 在主网上运行之前,您应该始终在测试网环境中测试您的智能合约。编写尽可能多的测试场景并运行多次测试迭代。 12.6 记录测试用例 保留您运行的测试用例的列表,以便您拥有它们以供参考和验证。 12.7 考虑正式验证 形式验证的主题太大,无法在此处充分涵盖。您可以假设您的代码将在敌对环境中运行,在该环境中,技术娴熟、资源丰富且不择手段的参与者将等待突袭任何漏洞。

Continue reading

Plutus教程:#9 抵押机制

9. 关于抵押机制 alonzo 在卡尔达诺开启了智能合约时代。抵押机制是一项重要功能,旨在确保智能合约的成功执行。依靠 Alonzo 分类账的确定性设计提供的保证,Cardano 实现了两阶段验证方案。引入两阶段验证的主要原因是限制节点未补偿的验证工作量。每个阶段都为实现这一目标服务: 第一阶段检查交易是否正确构造,是否可以支付其处理费 第二阶段运行交易中包含的脚本 如果事务是第 1 阶段有效,则运行第 2 阶段脚本。如果第 1 阶段失败,则不会运行任何脚本,并立即丢弃该事务。抵押品用于保证在第 2 阶段验证失败的情况下节点的工作得到补偿。因此,抵押品是用户提供的货币保证,以确保合约经过精心设计和彻底测试。抵押金额在构建交易时指定。不是直接,而是通过向交易添加抵押品输入。与这些特别标记的输入对应的 UTXO 中的总余额是交易的抵押品金额。如果用户满足担保条件,并且合约得到执行,则抵押品是安全的。 9.1 场景 没有抵押品,如果智能合约失败,则不会向用户收费。然而,当交易失败时,网络已经产生了一些成本来启动和验证交易。这意味着恶意行为者可以用无效交易淹没网络,以很少的成本拒绝向其他用户提供服务。 9.2 解决方案 当用户发起交易时,他们会提交足够的 ada 来支付其执行成本。在 Alonzo 中,调用和使用非本地智能合约(称为阶段 2 合约)的交易也需要足够的抵押品来支付与潜在交易失败相关的成本。这个数量可能很小,但足以使拒绝服务 (DOS) 攻击的成本高得令人望而却步。仅当交易未通过验证时才会收取抵押费用。如果合约通过验​​证,则收取交易费用,但不收取抵押品。 9.3 推理 诚实的用户永远不会面临失去抵押品的风险。Cardano 区块链在交易成本方面是确定性的,因为这些成本仅取决于本地价值和本地状态。这意味着用户可以在提交交易之前计算交易的执行成本(以 ada 为单位)。此功能不同于其他区块链,包括以太坊,其他网络活动会影响 gas 成本。所需的抵押品数量仅取决于执行成本。Cardano 测试网提供了一个安全的环境和免费的测试 ada,因此分布式应用程序 (DApp) 开发人员可以在部署到主网之前彻底测试他们的智能合约。如果测试网上的交易成功,开发人员可以完全确定所有脚本确实会成功。如果自交易构建以来链上条件发生变化,则该交易将被完全拒绝,并且不会收取任何费用。例如,如果缺少签名,则不会收取任何抵押品。 9.4 技术细节 抵押品一词是指抵押品输入所引用的 UTXO 中包含的总 ada。如果第 2 阶段脚本失败,则交易使用抵押品输入来支付其费用。Shelley 正式规范引入了“多重签名”脚本的概念。诸如此类的第一阶段脚本完全由分类帐规则捕获。因此,可以在实施处理之前轻松评估执行成本,并且可以根据包含脚本的交易大小直接在分类账规则实施中计算任何费用。相比之下,阶段 2 脚本可以执行任意(原则上,图灵完备)计算。我们要求使用第 2 阶段脚本的事务在许多抽象 ExUnit 方面有一个预算。该预算根据许多特定指标(包括内存使用或抽象执行步骤)给出了资源使用的定量限制。然后将预算用作交易费用计算的一部分。 以上摘自 Alonzo 网络的正式规范。有关更多详细信息,请阅读 Plutus Core 的 Cardano 分类帐规范。

Continue reading

Haskell教程:#14 Zippers

Zippers Haskell 中的拉链基本上是指向数据结构(例如树)的某个特定位置的指针。 让我们考虑具有 5 个元素 [45,7,55,120,56] 的树,它可以表示为完美二叉树。如果我想更新这个列表的最后一个元素,那么在更新它之前我需要遍历所有元素以到达最后一个元素。对?但是,如果我们能够以这样一种方式构建我们的树,即具有 N 个元素的树是 [(N-1),N] 的集合,那会怎样呢?然后,我们不需要遍历所有不需要的 (N-1) 元素。我们可以直接更新第 N 个元素。这正是拉链的概念。它聚焦或指向树的特定位置,我们可以在该位置更新该值,而无需遍历整棵树。 在下面的例子中,我们在列表中实现了拉链的概念。同样,可以在树或文件数据结构中实现 Zipper。 data List a = Empty | Cons a (List a) deriving (Show, Read, Eq, Ord) type Zipper_List a = ([a],[a]) go_Forward :: Zipper_List a -> Zipper_List a go_Forward (x:xs, bs) = (xs, x:bs) go_Back :: Zipper_List a -> Zipper_List a go_Back (xs, b:bs) = (b:xs, bs) main = do let list_Ex = [1,2,3,4] print(go_Forward (list_Ex,[])) print(go_Back([4],[3,2,1])) 当您编译并执行上述程序时,它将产生以下输出 -

Continue reading

Haskell教程:#13 Monads

Monads Monad 只不过是一种具有一些额外功能的 Applicative Functor。它是一个 Type 类,它管理着三个基本规则,称为 monadic 规则。所有三个规则都严格适用于 Monad 声明,如下所示 class Monad m where return :: a -> m a (>>=) :: m a -> (a -> m b) -> m b (>>) :: m a -> m b -> m b x >> y = x >>= \_ -> y fail :: String -> m a fail msg = error msg 适用于 Monad 声明的三项基本法律是 Left Identity Law - 返回函数不会改变值,也不应该改变 Monad 中的任何内容。可以表示为“return »> mf = mf”。 Right Identity Law - 返回函数不会改变值,也不应该改变 Monad 中的任何内容。可以表示为“mf >=> return = mf”。 Associativity - 根据这个定律,Functors 和 Monad 实例应该以相同的方式工作。它可以在数学上表示为“( f >==>g) >=> h =f >= >(g >=h)”。 前两个定律迭代相同的点,即返回值应该在绑定运算符的两侧具有标识行为。我们在前面的例子中已经使用了很多 Monad,却没有意识到它们是 Monad。考虑以下示例,其中我们使用 List Monad 生成特定列表。

Continue reading

Haskell教程:#12 Functor

函子(Functor) Haskell 中的函子是一种可以映射的不同类型的函数表示。 它是实现多态的高级概念。 根据 Haskell 开发者的说法,List、Map、Tree 等所有类型都是 Haskell Functor 的实例。Functor 是一个内置类,其函数定义类似于 class Functor f where fmap :: (a -> b) -> f a -> f b 根据这个定义,我们可以得出结论,Functor 是一个函数,它接受一个函数,比如 fmap() 并返回另一个函数。 在上面的例子中, fmap() 是函数 map() 的一般化表示。在下面的例子中,我们将看到 Haskell Functor 是如何工作的。 main = do print(map (subtract 1) [2,4,8,16]) print(fmap (subtract 1) [2,4,8,16]) 在这里,我们在列表上使用了 map() 和 fmap() 进行减法运算。 您可以观察到这两个语句将产生包含元素 [1,3,7,15] 的列表的相同结果。这两个函数都调用了另一个名为subtract() 的函数来产生结果。 [1,3,7,15] [1,3,7,15] 那么,map和fmap有什么区别呢? 不同之处在于它们的用法。 Functor 使我们能够在不同的数据类型中实现更多的功能主义者,例如“just”和“Nothing”。 main = do print (fmap (+7)(Just 10)) print (fmap (+7) Nothing) 上面的代码将在终端上产生以下输出

Continue reading

Haskell教程:#11 输入&输出

输入 & 输出 到目前为止,我们讨论的所有示例本质上都是静态的。在本章中,我们将学习与用户进行动态通信。我们将学习 Haskell 中使用的不同输入和输出技术。 文件和流(File & Streams) 到目前为止,我们已经对程序本身的所有输入进行了硬编码。我们一直在从静态变量中获取输入。现在,让我们学习如何从外部文件读取和写入。让我们创建一个文件并将其命名为“abc.txt”。接下来,在此文本文件中输入以下几行:“在这里,您将获得学习 Haskell 的最佳资源。” 接下来,我们将编写以下代码,该代码将在控制台上显示此文件的内容。在这里,我们使用函数 readFile() 读取文件直到找到 EOF 字符。 main = do let file = "abc.txt" contents <- readFile file putStrLn contents 上面的代码将文件“abc.txt”作为字符串读取,直到遇到任何文件结束字符。这段代码将生成以下输出。 在这里,您将获得学习 Haskell 的最佳资源。 命令行参数 Haskell 还提供了通过命令提示符操作文件的工具。让我们回到终端并输入“ghci”。然后,键入以下命令集 - let file = "abc.txt" writeFile file "I am just experimenting here." readFile file 在这里,我们创建了一个名为“abc.txt”的文本文件。接下来,我们使用命令 writeFile 在文件中插入了一条语句。最后,我们使用命令 readFile 在控制台上打印文件的内容。我们的代码将产生以下输出 - I am just experimenting here. 例外 异常可以被视为代码中的错误。这是编译器在运行时没有得到预期输出的情况。像任何其他优秀的编程语言一样,Haskell 提供了一种实现异常处理的方法。 如果您熟悉 Java,那么您可能知道 Try-Catch 块,我们通常会在该块中抛出错误并在 catch 块中捕获相同的错误。在 Haskell 中,我们也有相同的函数来捕获运行时错误。

Continue reading

Haskell教程:#10 模块

模块(Modules) 如果您使用过 Java,那么您就会知道所有类是如何绑定到一个名为 package.json 的文件夹中的。 同样,Haskell 可以被视为模块的集合。Haskell 是一种函数式语言,一切都表示为表达式,因此模块可以称为相似或相关类型函数的集合。您可以将一个模块中的函数导入到另一个模块中。 在开始定义其他函数之前,所有“import”语句都应该放在第一位。 在本章中,我们将学习 Haskell 模块的不同特性。 List Module List 提供了一些很棒的函数来处理列表类型的数据。 导入 List 模块后,您可以使用多种功能。在以下示例中,我们使用了 List 模块下的一些重要功能。 import Data.List main = do putStrLn("Different methods of List Module") print(intersperse '.' "Tutorialspoint.com") print(intercalate " " ["Lets","Start","with","Haskell"]) print(splitAt 7 "HaskellTutorial") print (sort [8,5,3,2,1,6,4,2]) 在这里,我们有很多函数甚至没有定义它们。 这是因为这些函数在 List 模块中可用。 导入 List 模块后,Haskell 编译器使所有这些函数在全局命名空间中可用。 因此,我们可以使用这些函数。我们的代码将产生以下输出 Different methods of List Module "T.u.t.o.r.i.a.l.s.p.o.i.n.t...c.o.m" "Lets Start with Haskell" ("Haskell","Tutorial") [1,2,2,3,4,5,6,8] Char Module Char 模块有很多预定义的函数来处理 Character 类型。 看看下面的代码块

Continue reading

Haskell教程:#9 函数组合

函数组合(Function Composition) 函数组合是将一个函数的输出用作另一个函数的输入的过程。 如果我们学习组合背后的数学会更好。 在数学中,合成用 f{g(x)} 表示,其中 g() 是一个函数,其输出用作另一个函数的输入,即 f()。如果一个函数的输出类型与第二个函数的输入类型匹配,则可以使用任意两个函数来实现函数组合。 我们使用点运算符 (.) 来实现 Haskell 中的函数组合。 看看下面的示例代码。 在这里,我们使用了函数组合来计算输入数是偶数还是奇数。 eveno :: Int -> Bool noto :: Bool -> String eveno x = if x `rem` 2 == 0 then True else False noto x = if x == True then "This is an even Number" else "This is an ODD number" main = do putStrLn "Example of Haskell Function composition" print ((noto.eveno)(16)) 在这里,在 main 函数中,我们同时调用了两个函数 noto 和 eveno。 编译器将首先以 16 作为参数调用函数“eveno()”。 此后,编译器将使用 eveno 方法的输出作为 noto() 方法的输入。它的输出如下 -

Continue reading

Haskell教程:#8 更高函数

更多函数 到目前为止,我们已经讨论了多种类型的 Haskell 函数并使用不同的方式来调用这些函数。 在本章中,我们将学习一些无需导入任何特殊 Type 类即可在 Haskell 中轻松使用的基本函数。 大多数这些函数是其他高阶函数的一部分。 Head 函数 Head 函数适用于列表。 它返回输入参数的第一个,它基本上是一个列表。 在下面的示例中,我们传递了一个包含 10 个值的列表,并使用 head 函数生成该列表的第一个元素。 main = do let x = [1..10] putStrLn "Our list is:" print (x) putStrLn "The first element of the list is:" print (head x) 上面代码运行结果如下 Our list is: [1,2,3,4,5,6,7,8,9,10] The first element of the list is: 1 Tail 函数 Tail 是补充 head 功能的功能。 它接受一个列表作为输入并生成没有头部部分的整个列表。 这意味着,tail 函数返回没有第一个元素的整个列表。 看看下面的例子 main = do let x = [1.

Continue reading

Haskell教程:#7 函数

Function 函数在 Haskell 中扮演着重要角色,因为它是一种函数式编程语言。 和其他语言一样,Haskell 也有自己的函数定义和声明。函数声明由函数名称及其参数列表及其输出组成。函数定义是您实际定义函数的地方。 让我们以 add 函数的小例子来详细理解这个概念。 add :: Integer -> Integer -> Integer --function declaration add x y = x + y --function definition main = do putStrLn "The addition of the two numbers is:" print(add 2 5) --calling a function 在这里,我们在第一行和第二行中声明了我们的函数,我们编写了我们的实际函数,它将接受两个参数并产生一个整数类型的输出。像大多数其他语言一样,Haskell 从 main 方法开始编译代码。 我们的代码将生成以下输出 The addition of the two numbers is: 7 模式匹配 (Pattern Matching) 模式匹配是匹配特定类型表达式的过程。 它只不过是一种简化代码的技术。 这种技术可以实现到任何类型的 Type 类中。 If-Else 可以用作模式匹配的替代选项。模式匹配可以被认为是动态多态的一种变体,在运行时,可以根据参数列表执行不同的方法。 看看下面的代码块。 这里我们使用了模式匹配技术来计算一个数的阶乘。 fact :: Int -> Int fact 0 = 1 fact n = n * fact ( n - 1 ) main = do putStrLn "The factorial of 5 is:" print (fact 5) 我们都知道如何计算一个数的阶乘。 编译器将开始搜索带有参数的名为“fact”的函数。 如果参数不等于 0,那么该数字将继续调用相同的函数,并且比实际参数的数字小 1。当参数的模式与 0 完全匹配时,它将调用我们的模式,即“fact 0 = 1”。 我们的代码将产生以下输出

Continue reading