本文深入探讨了Solidity 0.8.19版本中引入的用户定义运算符(UDO)及其与用户定义值类型(UDVT)的结合使用。UDVT允许创建更具描述性的数据类型别名,而UDO则通过using for
语句将函数与运算符符号关联,简化了UDVT的算术运算,提高了代码的可读性和可维护性。文章通过代码示例详细解释了UDVT和UDO的定义、使用方式以及使用规则,并探讨了它们在数学库等场景中的应用。
Solidity 0.8.19 带来了一些变化和升级。
在这篇文章中,我们将深入探讨用户定义值类型的用户定义运算符。
✍🏻 在本文的其余部分,我将它们称为以下内容:a. 用户定义运算符 - UDOsb. 用户定义值类型 - UDVT
在学习 UDO 之前,我们需要了解 UDVT。
UDVT 是 Solidity 中预定义值类型之上的 gasless 值类型抽象。这是在 Solidity 0.8.8 中引入的。
简单来说,我们可以将它们称为其他值类型的别名。引入它的核心原因是方便对变量进行更严格的定义。
例如,我们的合约存储两种类型的地址,一种用于买家,另一种用于卖家。我们已经知道,在底层,这两个变量都将是 address 类型,但是
在引入 UDVT 之前,这个目标是通过结构体实现的。下面是一个例子。
使用结构体定义更具描述性的数据类型(消耗 gas)
此代码中的四个函数用于将给定的数据类型包装或解包为或从定义的数据类型。
我相信你在一些合约中见过这种用法。但是,正如我们所知,结构体是引用类型,它们会使用内存,比仅使用 uint 消耗更多的 gas。这就是使用 UDVT 的地方。它们是简单的提取,不消耗 gas。
就语法而言,我们可以像 type A is B
这样定义 UDVT,其中 A
是提取的值类型,也可以称为 B
的别名,B
是可以是 uint、address 等的底层类型。一旦我们声明了这些值类型,我们就会得到两个附加的方法,wrap
和 unwrap
,A.wrap(value)
可用于将底层类型转换为新创建的值类型,A.unwrap(value)
用于反之亦然。
使用 UDVT 定义更具描述性的数据类型(零成本)
这是与之前代码片段相关的演示,但是,你可能已经意识到我们不需要显式定义包装/解包函数,因为 Solidity 实际上给出了包装/解包函数。因此,如果我们谈论这些函数的使用,下面是另一个代码片段。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;
// Represent a 18 decimal, 256 bit wide fixed point type
// using a user defined value type.
type UFixed is uint256;
/// A minimal library to do fixed point operations on UFixed.
uint256 constant multiplier = 10**18;
/// Adds two UFixed numbers. Reverts on overflow,
/// relying on checked arithmetic on uint256.
function add(UFixed a, UFixed b) internal pure returns (UFixed) {
return UFixed.wrap(UFixed.unwrap(a) + UFixed.unwrap(b));
}
/// Multiplies UFixed and uint256. Reverts on overflow,
/// relying on checked arithmetic on uint256.
function mul(UFixed a, uint256 b) internal pure returns (UFixed) {
return UFixed.wrap(UFixed.unwrap(a) * b);
}
/// Take the floor of a UFixed number.
/// @return the largest integer that does not exceed `a`.
function floor(UFixed a) internal pure returns (uint256) {
return UFixed.unwrap(a) / multiplier;
}
/// Turns a uint256 into a UFixed of the same value.
/// Reverts if the integer is too large.
function toUFixed(uint256 a) internal pure returns (UFixed) {
return UFixed.wrap(a * multiplier);
}
}
我们可以得出结论,UDVT 不会影响合约的逻辑,而是一种语法糖,使我们的代码更清晰易读。例如,PRB 数学库,该库用于解决 Solidity 中的浮点数问题,使用 UDVT 来定义不同类型的浮点数。
现在,当我们尝试对 UDVT 使用算术运算时,问题就出现了。正如你在上面的例子中看到的,我们必须解包传递的变量才能使用内置运算符。这就是引入 UDO 的地方。
UDO 是使用 Solidity 的两个内置功能构建的,即内置运算符和 using … for …
。
UDO 基本上是 using for
的扩展版本。回顾一下,using for
用于:
using LibrayName for TypeName
using LibrayName for *
using {LibrayName.FunctionName, FreeFunctionName} for TypeName
现在 UDO 开始发挥作用,并定义了第四种
using for
语句。
类似这样 👇:
using {FunctionName as OperatorSign} for UDVTName global;
此语法有助于将函数作为运算符符号附加,与附加库函数相同,不同之处在于我们可以使用运算符符号而不是使用调用函数的默认方式。通过查看下面的代码示例,这将更清楚。
使用用户定义运算符时,有一些规则。
它们只能定义为自由函数(在文件级别定义的函数)。
自由函数应该是 pure 的。
只能在全局 using for
指令中定义,即它不能在合约内部,而是在文件级别。
用户定义的运算符只能附加到 UDVT,而不能附加到基础的默认值类型。
它们只能为特定类型定义,即它们不能在同一函数中使用不同的 UDVT。
最后,在将运算符附加到任何 UDVT 时,只能使用以下运算符符号:&, |, ^, ~, +, -, *, /, %, ==, !=, <, <=, >, >=.
现在让我们看一个代码示例。
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
//The types are defined at the file level
//类型在文件级别定义
type Float is uint256;
type unFloat is uint256;
//"using for" statement are written at global directive
//not inside a contract
//"using for" 语句写在全局指令中
//不在合约内部
using {add as +} for Float global;
using {multiply as *} for Float global;
using {divide as /} for Float global;
//These are pure free functions, which is a requirement
//这些是 pure 的自由函数,这是必需的
///@notice Each function works with only one type.
function add(Float a, Float b) pure returns (Float) {
return Float.wrap(Float.unwrap(a) + Float.unwrap(b));
}
function multiply(Float a, Float b) pure returns (Float) {
return Float.wrap(Float.unwrap(a) * Float.unwrap(b));
}
function divide(Float a, Float b) pure returns (Float) {
return Float.wrap(Float.unwrap(a) / Float.unwrap(b));
}
//Using the attached operators inside a contract
//在合约中使用附加的运算符
contract UDO {
Float cent = Float.wrap(100);
Float decimal = Float.wrap(1e18);
//The multiplication and division using operators is only possible
// because we attached these particular operators sign to the relavant functions
//使用运算符进行乘法和除法是可能的
// 因为我们将这些特定的运算符符号附加到相关的函数
function takePercent(Float _amount, Float totalAmount)
external
view
returns (Float)
{
return (_amount * cent * decimal)/(totalAmount);
}
}
将任何函数附加到任何 UDVT 独立于将同一函数绑定到它。这意味着当我们以运算符的形式绑定自由函数时,我们不能以类似于库函数的方式使用 UDVT 调用自由函数,例如 add(a,b)
或 a.add(b)
,但是,当它们作为函数附加时,我们显然可以这样做。
让我们看看下面的例子 .
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
type Float is uint256;
type unFloat is uint256;
// binding and attaching
// 绑定和附加
using {multiply as *,multiply} for Float global;
using {divide as /} for Float global;
function multiply(Float a, Float b) pure returns (Float) {
return Float.wrap(Float.unwrap(a) * Float.unwrap(b));
}
function divide(Float a, Float b) pure returns (Float) {
return Float.wrap(Float.unwrap(a) / Float.unwrap(b));
}
contract UDO {
Float cent = Float.wrap(100);
Float decimal = Float.wrap(1e18)
function takePercent(Float _amount, Float totalAmount)
external
view
returns (Float)
{
// return (_amount * cent * decimal)/(totalAmount);
return (_amount.multiply(decimal.multiply(cent))).divide(totalAmount);
//In this line Multiply will work but divide will throw an error, because
// we haven't binded the divide function to Float.
//在这一行中,Multiply 可以工作,但 divide 会抛出错误,因为
// 我们没有将 divide 函数绑定到 Float。
}
}
我们用于特定函数的符号不是严格需要的,我们可以使用 /
代替 +
与 add 函数,并且 /
将用作 add 函数。但是,这会引起不必要的混乱,所以我们不会这样做。
正如之前讨论的,UDVT 可用于防止任何类型的类型错误,并提供对逻辑的更好理解,UDO 通过使其更易于访问和熟悉来提高其可用性。
这些最好的用法是数学库,我们可以在其中拥有多种类型的浮点数等等。
这就是本文的全部内容,加入我们,提出你所有的 Solidity 问题/困惑 这里。
- 原文链接: decipherclub.com/user-de...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!