美文网首页区块链技术文章
Solidity 学习笔记(1)- string和bytes

Solidity 学习笔记(1)- string和bytes

作者: kamiSDY | 来源:发表于2018-04-10 21:17 被阅读2196次

    固定长度的bytes转化为string

    如果是固定大小字节数组转string,那么就需要先将字节数组转动态字节数组,再转字符串。

    pragma solidity ^0.4.4;
    
    contract C {
    
       function byte32ToString(bytes32 b) constant returns (string) {
           
           bytes memory names = new bytes(b.length);
           
           for(uint i = 0; i < b.length; i++) {
               
               names[i] = b[i];
           }
           
           return string(names);
       }
       
    

    但是,如果字符串不是占满32个字节。那么后面就会由\u0000进行填充。所以我们需要将这些空字符去掉。
    改进的方法:

    pragma solidity ^0.4.4;
    
    contract C {
        
        function bytes32ToString(bytes32 x) constant returns (string) {
            bytes memory bytesString = new bytes(32);
            uint charCount = 0;
            for (uint j = 0; j < 32; j++) {
                byte char = byte(bytes32(uint(x) * 2 ** (8 * j)));
                if (char != 0) {
                    bytesString[charCount] = char;
                    charCount++;
                }
            }
            bytes memory bytesStringTrimmed = new bytes(charCount);
            for (j = 0; j < charCount; j++) {
                bytesStringTrimmed[j] = bytesString[j];
            }
            return string(bytesStringTrimmed);
        }
    
        function bytes32ArrayToString(bytes32[] data) constant returns (string) {
            bytes memory bytesString = new bytes(data.length * 32);
            uint urlLength;
            for (uint i = 0; i< data.length; i++) {
                for (uint j = 0; j < 32; j++) {
                    byte char = byte(bytes32(uint(data[i]) * 2 ** (8 * j)));
                    if (char != 0) {
                        bytesString[urlLength] = char;
                        urlLength += 1;
                    }
                }
            }
            bytes memory bytesStringTrimmed = new bytes(urlLength);
            for (i = 0; i < urlLength; i++) {
                bytesStringTrimmed[i] = bytesString[i];
            }
            return string(bytesStringTrimmed);
        }    
    }
    

    其中在进行char的转换时使用了一个算法。这里针对单字符转化给一个更清晰的例子:

    pragma solidity ^0.4.4;
    
    contract C {
        
        // 0x6c
        
        function uintValue() constant returns (uint) {
            
            return uint(0x6c);
        }
        
        function bytes32To0x6c() constant returns (bytes32) {
            
            return bytes32(0x6c);
        }
        
        function bytes32To0x6cLeft00() constant returns (bytes32) {
            
            return bytes32(uint(0x6c) * 2 ** (8 * 0));
        }
        
        function bytes32To0x6cLeft01() constant returns (bytes32) {
            
            return bytes32(uint(0x6c) * 2 ** (8 * 1));
        }
        
        function bytes32To0x6cLeft31() constant returns (bytes32) {
            
            return bytes32(uint(0x6c) * 2 ** (8 * 31));
        }
    }
    

    我们可以看到:
    bytes32(uint(0x6c) * 2 ** (8 * 31))就是将6c左移31位;
    bytes32(uint(0x6c) * 2 ** (8 * 1))就是将6c左移1位;

    所以,通过byte(bytes32(uint(x) * 2 ** (8 * j)))获取到的始终是第0个字节。

    image.png

    最后在说明一点:
    string本身是一个特殊的动态字节数组,所以它只能和bytes之间进行转换,不能和固定大小字节数组进行直接转换,如果是固定字节大小数组,需要将其转换为动态字节大小数组才能进行转换。

    应用中Hash string转化为solidity的byte32数组

    如果我们在nodeJs中使用某些算法获得hash的string,例如IPFS的hash,如果智能合约的func的参数值设置为bytes32,那么我们就需要将这些hash值转化成solidity的bytes32[]数组:

    function ipfsHashToBytes32(ipfs_hash) {
        var h = bs58.decode(ipfs_hash).toString('hex').replace(/^1220/, '');
        if (h.length != 64) {
            console.log('invalid ipfs format', ipfs_hash, h);
            return null;
        }
        return '0x' + h;
    }
    
    function bytes32ToIPFSHash(hash_hex) {
        //console.log('bytes32ToIPFSHash starts with hash_buffer', hash_hex.replace(/^0x/, ''));
        var buf = new Buffer(hash_hex.replace(/^0x/, '1220'), 'hex')
        return bs58.encode(buf)
    }
    

    string?尽量不要用

    最近,写了点智能合约,想用string试一试,写了一段频繁更改map的value值的方法。

    mapping (string=>uint) content;
    mapping (address=>string) relation;
    function temp(string hash, uint price) public {
            content[hash] = price;
            relation[msg.sender] = hash;
        }
    

    结果运行之后,出现了error:
    Error: VM Exception while processing transaction: out of gas.
    后来查阅了资料才发现:
    原来在solidity的contract中是一个非常昂贵的资源。尽量不要用,推荐使用固定长度的bytes数组来进行替代。

    bytes32

    在编写过程中,可能会有很多人从web3,remix以及eth wallet中获得的bytes32值并不同。
    比如说,合约:

    pragma solidity ^0.4.11;
    
    contract ABC{
    
        struct Data{
            bytes32 data;
            bytes32 data2;
            bytes32 data3;
            bytes32 data4;
            bytes32 data5;
        }
        mapping(uint => Data) public metaData;
    
        function ABC(){
    
        }
    
        function addData(bytes32 data,
            bytes32 data2,
            bytes32 data3,
            bytes32 data4,
            bytes32 data5){
            metaData[0]=Data(data,data2,data3,data4,data5);
        }
    
        function getData() returns(bytes32,bytes32,bytes32,bytes32,bytes32){
            return (metaData[0].data,metaData[0].data2,metaData[0].data3,metaData[0].data4,metaData[0].data5);
        }
    }
    

    输入参数:
    "d4967590eb024589dfb6b9e48a576eb49ebc19d764b0d1d67dc21975e7258e97", "1", "1", "1", "065e0be95fb43db528a20ba65c0e575e33cd4a9e1ca089dba4efff24596e8553"

    使用Remix:
    [图片上传失败...(image-d3e943-1523366258073)]
    数据为:

    0: bytes32: data 0x6434393637353930656230323435383964666236623965343861353736656234
    1: bytes32: data2 0x3100000000000000000000000000000000000000000000000000000000000000
    2: bytes32: data3 0x3100000000000000000000000000000000000000000000000000000000000000
    3: bytes32: data4 0x3100000000000000000000000000000000000000000000000000000000000000
    4: bytes32: data5 0x3036356530626539356662343364623532386132306261363563306535373565
    

    以太坊wallet给出的数据:


    image.png

    数据为原始数据,但是每个数据都在之前加了一个0x。

    0: bytes32: data 0xd4967590eb024589dfb6b9e48a576eb49ebc19d764b0d1d67dc21975e7258e97
    1: bytes32: data2 0x1000000000000000000000000000000000000000000000000000000000000000
    2: bytes32: data3 0x1000000000000000000000000000000000000000000000000000000000000000
    3: bytes32: data4 0x1000000000000000000000000000000000000000000000000000000000000000
    4: bytes32: data5 0x065e0be95fb43db528a20ba65c0e575e33cd4a9e1ca089dba4efff24596e8553
    

    对于Web3.js
    打印出来的数值和remix一样,但是书顺序变了,应该是针对数值进行了排序?

    问题分析

    因为solidity支持的bytes32,JavaScript并没有原生的数据类型进行支持。直接使用string并不能够直接转换到bytes32。推荐在直接传递byte数组给evm。如果我们想直接传递这个string,我们需要:

    • 为其添加一个0x十六进制的前缀
    • 为其补齐(右靠齐)相对位数的0字符(64个字符)
      例如:
    ["0xd4967590eb024589dfb6b9e48a576eb49ebc19d764b0d1d67dc21975e7258e97",
    "0x0000000000000000000000000000000000000000000000000000000000000001",
    "0x0000000000000000000000000000000000000000000000000000000000000001",
    "0x0000000000000000000000000000000000000000000000000000000000000001",
    "0x065e0be95fb43db528a20ba65c0e575e33cd4a9e1ca089dba4efff24596e8553"]
    

    为了验证这个推断,我们写个小程序-string和hexStr(或者是byte数组)之间相互转化的方法。然后测试一下:

    string:
    d4967590eb024589dfb6b9e48a576eb49ebc19d764b0d1d67dc21975e7258e97
    
    hex:
    64 34 39 36 37 35 39 30 65 62 30 32 34 35 38 39 64 66 62 36 62 39 65 34 38 61 35 37 36 65 62 34 39 65 62 63 31 39 64 37 36 34 62 30 64 31 64 36 37 64 63 32 31 39 37 35 65 37 32 35 38 65 39 37
    

    所以,如果没有将string添加0x前缀,默认就会将其转化为64 bytes数组。而且针对没有满足64bytes的情况,例如1。则会出现两种情况

    • 将其视为0x31--也就是char 1。然后填充0来形成bytes32,或者是64位的hex数。
    • 直接将其视为hex数1,然后填充0来满足bytes32.

    参考链接

    相关文章

      网友评论

        本文标题:Solidity 学习笔记(1)- string和bytes

        本文链接:https://www.haomeiwen.com/subject/vdqohftx.html