如何解码智能合约中的字节calldata?

Ale*_*hin 6 solidity rsk ethers.js hardhat

我有 2 个交互的智能合约,正在Hardhat中开发/测试并部署到RSK。其中之一是具有功能的ERC1363 支付代币transferAndCall(address,uint256,bytes),第二个是代币接收器,buy(address,uint,uint,bytes3)我需要对其函数调用进行链下编码并发送到代币的transferAndCall函数bytes参数。ERC1363 合约将代币从发送者的账户转移到接收者智能合约的账户,然后在同一交易中调用接收者的onTransferReceived(address,address,uint256,bytes),其中最后一个bytes参数应该是编码buy函数调用。

这是我的接收者智能合约:

contract TokenReceiver is IERC1363Receiver {
  IERC1363 acceptedToken;
    
  constructor(IERC1363 _acceptedToken) {
    acceptedToken = _acceptedToken;
  }
  event PurchaseMade(address indexed sender, uint tokensPaid, uint productAmount, bytes3 color);
    
  function buy(address sender, uint tokensPaid, uint productAmount, bytes3 color) public {
    // allowed to be called only via the accepted token
    require(msg.sender == address(acceptedToken), "I accept purchases in Payable Tokens");
    emit PurchaseMade(sender, tokensPaid, productAmount, color);
  }

  function onTransferReceived(address operator, address sender, uint256 tokensPaid, bytes calldata data) external override (IERC1363Receiver) returns (bytes4) {
      // TODO: decode calldata and call `buy` function
    return this.onTransferReceived.selector;
  }
}
Run Code Online (Sandbox Code Playgroud)

这是我通过将函数的签名和参数编码buy在一起来组装调用数据的方法:

contract TokenReceiver is IERC1363Receiver {
  IERC1363 acceptedToken;
    
  constructor(IERC1363 _acceptedToken) {
    acceptedToken = _acceptedToken;
  }
  event PurchaseMade(address indexed sender, uint tokensPaid, uint productAmount, bytes3 color);
    
  function buy(address sender, uint tokensPaid, uint productAmount, bytes3 color) public {
    // allowed to be called only via the accepted token
    require(msg.sender == address(acceptedToken), "I accept purchases in Payable Tokens");
    emit PurchaseMade(sender, tokensPaid, productAmount, color);
  }

  function onTransferReceived(address operator, address sender, uint256 tokensPaid, bytes calldata data) external override (IERC1363Receiver) returns (bytes4) {
      // TODO: decode calldata and call `buy` function
    return this.onTransferReceived.selector;
  }
}
Run Code Online (Sandbox Code Playgroud)

我的问题是:

  • 如何解码接收者onTransferReceived函数内的呼叫数据?
  • 如何提取函数签名和其他 2 个编码参数,然后在接收器上调用相应的函数?

小智 6

您可以利用内联汇编来解码字节数据。创建一个pure包含以下内容的辅助函数:\n\xe2\x80\x8b

\n
function decode(bytes memory data) private pure returns(bytes4 selector, uint productAmount, bytes3 color) {\n    assembly {\n      // load 32 bytes into `selector` from `data` skipping the first 32 bytes\n      selector := mload(add(data, 32))\n      productAmount := mload(add(data, 64))\n      color := mload(add(data, 96))\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

\xe2\x80\x8b\n此处mload(0xAB)加载位于内存地址 0xAB 处的一个字(32 字节),并对add(0xAB, 0xCD)两个值求和\n\xe2\x80\x8b\n有关 Solidity 中内联汇编的更多信息,请参阅本文。\n\xe2 \x80\x8b\n接下来,您可以在合约中使用创建的函数:\n\xe2\x80\x8b

\n
(bytes4 selector, uint productAmount, bytes3 color) =\n  decode(data);\n
Run Code Online (Sandbox Code Playgroud)\n

\xe2\x80\x8b\n既然有了选择器和其他参数,就可以构造函数调用数据\n\xe2\x80\x8b

\n
bytes memory funcData =\n  abi.encodeWithSelector(selector, sender, tokensPaid, productAmount, color);\n
Run Code Online (Sandbox Code Playgroud)\n

\xe2\x80\x8b\n现在您可以进行低级调用来调用相应的函数\n\xe2\x80\x8b

\n
(bool success,) = address(this).call(funcData);\nrequire(success, "call failed");\n
Run Code Online (Sandbox Code Playgroud)\n

\xe2\x80\x8b\n警告:\n请记住,使用上述方法\n允许攻击者能够调用合约中的任何函数。\n请小心使用低级别调用。\n\xe2\x80\x8b \n为避免这种情况,请在调用之前验证函数选择器,\n如下所示:\n\xe2\x80\x8b

\n
if (selector == this.buy.selector) {\n    buy(sender, tokensPaid, productAmount, color);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

\xe2\x80\x8b\n因此,你的onTransferReceived函数可能看起来像这样:\n\xe2\x80\x8b

\n
function onTransferReceived(address operator, address sender, uint256 tokensPaid, bytes calldata data) external override (IERC1363Receiver) returns (bytes4) {\n    require(msg.sender == address(acceptedToken), "I accept purchases in Payable Tokens");\n\xe2\x80\x8b\n    (bytes4 selector, uint productAmount, bytes3 color) =\n        decode(data);\n\xe2\x80\x8b\n    if (selector == this.buy.selector) {\n      buy(sender, tokensPaid, productAmount, color);\n    }\n\xe2\x80\x8b\n    return this.onTransferReceived.selector;\n  }\n
Run Code Online (Sandbox Code Playgroud)\n


Der*_*awi 5

//Define struct within the contract, but outside the function
struct BuyParams {
        bytes4 buySigHash;
        uint256 productAmount;
        bytes3 color;
}

// within onTransferReceived: decode the calldata:
BuyParams memory decoded = abi.decode(
    data,
    (BuyParams)
);


//Now use as function arguments by passing:
decoded.buySigHash,
decoded.productAmount,
decoded.color,


Run Code Online (Sandbox Code Playgroud)

添加:您也许可以通过更改调用数据的顺序来节省gas:

from:
        bytes4 buySigHash;
        uint256 productAmount;
        bytes3 color;
to:
        uint256 productAmount;
        bytes4 buySigHash;
        bytes3 color;
Run Code Online (Sandbox Code Playgroud)