前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用solidity实现一个printf函数

使用solidity实现一个printf函数

作者头像
Tiny熊
发布2022-04-08 14:14:52
4380
发布2022-04-08 14:14:52
举报
文章被收录于专栏:深入浅出区块链技术

本文作者:jackson[1]

字符串格式化函数在应用开发时经常用到,而在合约中使用场景似乎没有那么多,然而要实现这个函数,则需要先解决一些问题,本文就探讨一下如何来解决这些问题。先看其用法:

代码语言:javascript
复制
printf("name=%s, age=%u, height=%u", n, a, h);

第一个问题,就是 printf 函数的参数类型和个数是动态变化的,然而 solidity 编译器目前并没有提供这种支持,如何解决这个问题呢?

方法一使用数组。使用数组是一种比较直接的想法,但是数组中的元素类型必须相同,这样的话,怎么传字符串呢?在计算机中,一切都是数据,可以考虑将字符串转为数值来传递,对于以太坊,一个 uint 是 256 位,32 个字节,拿出一位来保存长度,可以用 uint 表示最长 31 个字符的字符串,代码如下:

方法二利用内置函数。虽然 solidity 不支持定义可变参数的函数,但是一些内置函数可以,例如 abi.encode(),可以传入可变参数,并将这些参数编码成字节数组。然后在 printf 函数里面,按照对应的方式解码就可以了。下面是解码 uint 和 string 的代码。

readAbiUInt()用于从 abi.encode()编码后的字节数组的指定位置读取一个 uint,其中被注释掉的代码是基本实现,通过循环读取数据按规则解码实现,但是此方法效率较低,因此可以更改勇敢下面的方式实现,提高效率。

readAbiString()则是从 abi.encode()编码后的字节数组的指定位置读取一个 string。至于为什么要这么实现,则是由于 abi.encode()的编码规则确定的,如果有需要,我后面再写一篇文章详细介绍其编码规则。

通过以上两种方法,可以解决传参数的问题了,接下来就是要解析格式化字符串了,这涉及到一个算法,可以考虑使用“有限状态机”的方式来实现。

有限状态机看起来很神秘,但其实逻辑非常简单,在解析时,按照需要解析的逻辑定义一些状态,然后确定每种状态遇到什么条件就会进入另外一个状态,如此就可以将一个字符串按照指定的逻辑进行解析。

下面是格式化字符串需要定义的几个状态:0:初始状态,解析开始,或者完成一个格式描述串的处理,就回到初始状态。1:遇到描述符开始%。在 0 状态下,遇到%就进入此状态。2:描述符结束。在 1 状态下,如果遇到 u,d,s,%,就算描述符结束,这四个描述符分别对应:无符号整数、整数、字符串、%转义。分别按照对应的位置从前面传入的参数表中按照指定的含义读出数据,写入到输出,然后状态回到 0。

下面是实现代码

代码语言:javascript
复制
/// @dev Format to memory buffer
    /// @param buffer Target buffer
    /// @param index Start index in buffer
    /// @param format Format string
    /// @param abiArgs byte array of arguments encoded by abi.encode()
    /// @return New index in buffer
    function sprintf(
        bytes memory buffer,
        uint index,
        bytes memory format,
        bytes memory abiArgs
    ) internal pure returns (uint) {

        uint i = 0;
        uint pi = 0;
        uint ai = 0;
        uint state = 0;
        uint w = 0;

        while (i < format.length) {
            uint c = uint(uint8(format[i]));
   // 0. Normal
            if (state == 0) {
                // %
                if (c == 37) {
                    while (pi < i) {
                        buffer[index++] = format[pi++];
                    }
                    state = 1;
                }
                ++i;
            }
   // 1. Check if there is -
            else if (state == 1) {
                // %
                if (c == 37) {
                    buffer[index++] = bytes1(uint8(37));
                    pi = ++i;
                    state = 0;
                } else {
                    state = 3;
                }
            }
   // 3. Find width
            else if (state == 3) {
                while (c >= 48 && c <= 57) {
                    w = w * 10 + c - 48;
                    c = uint(uint8(format[++i]));
                }
                state = 4;
            }
            // 4. Find format descriptor
   else if (state == 4) {
                uint arg = readAbiUInt(abiArgs, ai);
                // d
                if (c == 100) {
                    if (arg >> 255 == 1) {
                        buffer[index++] = bytes1(uint8(45));
                        arg = uint(-int(arg));
                    } else {
                        buffer[index++] = bytes1(uint8(43));
                    }
                    c = 117;
                }
                // u
                if (c == 117) {
                    index = writeUIntDec(buffer, index, arg, w == 0 ? 1 : w);
                }
                // x/X
                else if (c == 120 || c == 88) {
                    index = writeUIntHex(buffer, index, arg, w == 0 ? 1 : w, c == 88);
                }
                // s/S
                else if (c == 115 || c == 83) {
                    index = writeAbiString(buffer, index, abiArgs, arg, w == 0 ? 31 : w, c == 83 ? 1 : 0);
                }
                // f
                else if (c == 102) {
                    if (arg >> 255 == 1) {
                        buffer[index++] = bytes1(uint8(45));
                        arg = uint(-int(arg));
                    }
                    index = writeFloat(buffer, index, arg, w == 0 ? 8 : w);
                }
                pi = ++i;
                state = 0;
                w = 0;
                ai += 32;
            }
        }

        while (pi < i) {
            buffer[index++] = format[pi++];
        }

        return index;
    }

这样,就可以实现一个 printf 函数了,又找回了 C 编程的感觉,虽说使用场景不多,但是并不代表没有,比如当我们需要按照某些规则来给一系列合约创建的代币生成名字的时候,就可以用这个方法了。

本文引用的代码来自:https://github.com/FORT-Protocol/FORT-V1.1/blob/nest4.0/contracts/libs/StringHelper.sol#L443[2]

参考资料

[1]

jackson: https://learnblockchain.cn/people/245

[2]

https://github.com/FORT-Protocol/FORT-V1.1/blob/nest4.0/contracts/libs/StringHelper.sol#L443: https://github.com/FORT-Protocol/FORT-V1.1/blob/nest4.0/contracts/libs/StringHelper.sol#L443

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-03-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 深入浅出区块链技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档