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



我有2个交互智能合约,我正在Hardhat中开发/测试并部署到RSK。其中一个是具有transferAndCall(address,uint256,bytes)函数的ERC1363可支付令牌,第二个是令牌接收者,我需要对其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;
}
}

这就是我如何通过编码buy函数的签名和参数来组装calldata:

it('buyer should be able to pay tokens and buy products in one transaction', async () => {
// TokenReceiver `buy` function signature hash: 0x85f16ff4
const buySigHash = tokenReceiver.interface.getSighash('buy');
// providing some product properties to the TokenReceiver
const productAmount = 99;
const color = '0x121212';
// packing `buy` signature and the properties together
const calldata = ethers.utils.defaultAbiCoder.encode(
['bytes4', 'uint256', 'bytes3'],
[buySigHash, productAmount, color],
);
// pay tokens and buy some products in one tx
const transferAndCallTx = payableToken
.connect(buyer)
['transferAndCall(address,uint256,bytes)'](tokenReceiver.address, tokenAmount, calldata);
await expect(transferAndCallTx)
.to.emit(tokenReceiver, 'PurchaseMade');
});
我的问题是:
  • 如何解码接收器的onTransferReceived函数内的calldata ?
  • 如何提取函数签名和其他2编码的参数,然后调用相应的函数在接收器吗?
//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,

添加:您可以通过更改calldata:

的顺序来节省gas。
from:
bytes4 buySigHash;
uint256 productAmount;
bytes3 color;
to:
uint256 productAmount;
bytes4 buySigHash;
bytes3 color;

您可以使用内联汇编来解码字节数据。创建purehelper函数,内容如下:

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

这里mload(0xAB)加载一个位于内存地址0xAB的字(32字节),add(0xAB, 0xCD)对两个值求和​有关solid中的内联组装的更多信息,请参阅本文。​接下来,这就是你如何在你的契约中使用创建的函数:

(bytes4 selector, uint productAmount, bytes3 color) =
decode(data);

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

bytes memory funcData =
abi.encodeWithSelector(selector, sender, tokensPaid, productAmount, color);

现在可以进行低级调用来调用相应的函数

(bool success,) = address(this).call(funcData);
require(success, "call failed");

:请记住,使用上述方法允许攻击者能够调用任何函数在你的合同里。使用低级别电话时要小心。​为了避免这种情况,请验证函数选择器,在调用之前,像这样:

if (selector == this.buy.selector) {
buy(sender, tokensPaid, productAmount, color);
}

因此,你的onTransferReceived函数可能看起来像这样:

function onTransferReceived(address operator, address sender, uint256 tokensPaid, bytes calldata data) external override (IERC1363Receiver) returns (bytes4) {
require(msg.sender == address(acceptedToken), "I accept purchases in Payable Tokens");
​
(bytes4 selector, uint productAmount, bytes3 color) =
decode(data);
​
if (selector == this.buy.selector) {
buy(sender, tokensPaid, productAmount, color);
}
​
return this.onTransferReceived.selector;
}

最新更新