ABI Encode 和 ABI Decode 使用教程

  • martin
  • 发布于 3天前
  • 阅读 75

在以太坊/Solidity里,ABIEncode和ABIDecode是把参数↔二进制数据(bytes)之间相互转换的机制,用于合约调用、跨合约通信、日志解析等。ABI(ApplicationBinaryInterface)是以太坊约定的一套数据编码/解码规则。即

以太坊 / Solidity 里,ABI EncodeABI Decode把参数 ↔ 二进制数据(bytes)之间相互转换的机制,用于合约调用、跨合约通信、日志解析等。

ABI (Application Binary Interface) 是以太坊约定的一套 数据编码 / 解码规则。即把人类可读的参数,转成 EVM 能识别的 bytes;再把 bytes,还原成人类可读的数据。

一、基本概念

ABI Encode: 将高级数据类型(如字符串、结构体、数组等)转换为字节码,用于合约调用。

ABI Decode: 将字节码解析回原始数据类型。

二、Solidity 中的使用

1. abi.encode()

将参数编码为标准 ABI 格式(带填充)。

特点:

  • 标准 ABI 编码
  • 每个参数 32 字节对齐
  • 用于 decode / 合约调用
  • 最常用、最安全
function encodeExample() public pure returns (bytes memory) {
    // 编码多个参数
    return abi.encode(123, "hello", true);
    // 返回: 0x000000000000000000000000000000000000000000000000000000000000007b
    //       0000000000000000000000000000000000000000000000000000000000000060
    //       0000000000000000000000000000000000000000000000000000000000000001
    //       0000000000000000000000000000000000000000000000000000000000000005
    //       68656c6c6f000000000000000000000000000000000000000000000000000000
}

2. abi.encodePacked()

紧密打包编码(不填充),节省空间,但可能导致哈希碰撞。

特点:

  • 紧凑编码(不补 32 字节)
  • 节省 gas
  • ⚠️ 不安全 decode
  • ⚠️ 不要用它做 decode

用途:

  • keccak256
  • 签名 / hash
function encodePackedExample() public pure returns (bytes memory) {
    // 紧密编码,无填充
    return abi.encodePacked(uint16(123), "hello", true);
    // 返回: 0x007b68656c6c6f01 (更短)
}

// 注意:可能的哈希碰撞问题
// abi.encodePacked("a", "bc") == abi.encodePacked("ab", "c") ❌

3. abi.encodeWithSelector()

编码函数选择器和参数,用于合约调用。

用途:

  • 手动构造函数调用
  • call / staticcall
function encodeWithSelectorExample() public pure returns (bytes memory) {
    // 编码函数调用: transfer(address,uint256)
    bytes4 selector = bytes4(keccak256("transfer(address,uint256)"));
    return abi.encodeWithSelector(
        selector,
        0x1234567890123456789012345678901234567890,
        100
    );
}

4. abi.encodeWithSignature()

直接使用函数签名编码。

encodeWithSelector 类似,但:

  • 运行时算 selector
  • 稍微贵一点
function encodeWithSignatureExample() public pure returns (bytes memory) {
    return abi.encodeWithSignature(
        "transfer(address,uint256)",
        0x1234567890123456789012345678901234567890,
        100
    );
}

5. abi.decode()

解码字节数据为原始类型。

规则:

  • 类型、顺序必须完全一致
  • 只能 decode abi.encode 的结果
  • 不能 decode encodePacked
function decodeExample(bytes memory data) public pure returns (uint256, string memory, bool) {
    // 解码数据
    (uint256 num, string memory str, bool flag) = abi.decode(
        data,
        (uint256, string, bool)
    );
    return (num, str, flag);
}

// 组合使用
function encodeAndDecode() public pure returns (uint256, string memory) {
    // 编码
    bytes memory encoded = abi.encode(uint256(42), "test");

    // 解码
    (uint256 num, string memory str) = abi.decode(encoded, (uint256, string));
    return (num, str);
}
对比项 ABI Encode ABI Decode
方向 参数 → bytes bytes → 参数
用途 调用 / 存储 解析返回值
常见方法 encode / encodePacked decode
是否补 32 字节 encode N/A
是否可逆 encode ✔ decode ✔

三、实际应用场景

1. 跨合约调用

contract Caller {
    function callOtherContract(address target) public {
        bytes memory data = abi.encodeWithSignature(
            "setValue(uint256)",
            123
        );
        (bool success, ) = target.call(data);
        require(success, "Call failed");
    }
}

2. 代理合约(Proxy)

contract Proxy {
    address public implementation;

    fallback() external payable {
        address impl = implementation;
        assembly {
            // 复制calldata
            calldatacopy(0, 0, calldatasize())
            // 委托调用
            let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
            // 复制返回数据
            returndatacopy(0, 0, returndatasize())
            // 返回或回滚
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }
}

3. 多签钱包

contract MultiSig {
    function executeTransaction(
        address to,
        uint256 value,
        bytes memory data
    ) public {
        // data 包含了 ABI 编码的函数调用
        (bool success, ) = to.call{value: value}(data);
        require(success, "Transaction failed");
    }
}

4. 事件日志解析

contract EventExample {
    event DataStored(uint256 indexed id, bytes data);

    function storeData(uint256 id, uint256 value, string memory text) public {
        bytes memory encodedData = abi.encode(value, text);
        emit DataStored(id, encodedData);
    }
}

四、Java/Web3j 中的使用

在您的项目中使用 Web3j:

// 编码参数
Function function = new Function(
    "transfer",
    Arrays.asList(new Address(toAddress), new Uint256(amount)),
    Collections.emptyList()
);
String encodedFunction = FunctionEncoder.encode(function);

// 解码返回值
List<Type> results = FunctionReturnDecoder.decode(
    responseData,
    function.getOutputParameters()
);

五、重要注意事项

  1. 类型匹配: 解码时必须指定正确的类型顺序
  2. 动态类型: string、bytes、数组等需要额外的长度信息
  3. encodePacked 风险: 不适合签名验证,可能导致碰撞
  4. Gas 消耗: encode 比 encodePacked 消耗更多 gas
  5. 版本兼容: 确保 Solidity 版本支持这些函数 (>= 0.4.24)

六、选择建议

场景 推荐方法
合约调用 encodeWithSignatureencodeWithSelector
哈希计算 encode (避免碰撞)
节省空间 encodePacked (非签名场景)
数据传输 encode (标准格式)

七、ABI 在合约调用中的位置

调用流程(重点)

函数名 + 参数
   ↓
ABI Encode
   ↓
calldata
   ↓
EVM 执行
   ↓
返回 bytes
   ↓
ABI Decode
   ↓
返回值

八、常见坑(⚠️ 很重要)

❌ 1. 用 encodePacked 去 decode

bytes memory data = abi.encodePacked(a, b);
abi.decode(data, (uint256, uint256)); // ❌

✔ 正确:

  • encodePacked 只用于 hash

❌ 2. Decode 类型顺序错误

abi.decode(data, (address, uint256)); // ❌

必须和 encode 时 一模一样


❌ 3. 动态类型没注意

动态类型:

  • string
  • bytes
  • uint[]
  • struct(包含动态成员)

👉 ABI encode 会放 offset + length + data


点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
martin
martin
xxx