I have 2 interacting smart contracts which I am developing/testing in Hardhat and deploying to RSK. One of them is an ERC1363 payable token with transferAndCall(address,uint256,bytes)
function, and the second one is a token receiver whose buy(address,uint,uint,bytes3)
function call I need to encode off-chain and send to the token's transferAndCall
function bytes
parameter. The ERC1363 contract transfers tokens from sender's account to the receiver smart contract's account and then within the same transaction calls receiver's onTransferReceived(address,address,uint256,bytes)
, where the last bytes
parameter should be encoded buy
function call.
This is my receiver smart contract:
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;
}
}
This is how I assemble the calldata by encoding the signature and params of the buy
function together:
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');
});
My question is:
onTransferReceived
function?//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,
Addition: You might be able to save gas by changing the order of the calldata:
from:
bytes4 buySigHash;
uint256 productAmount;
bytes3 color;
to:
uint256 productAmount;
bytes4 buySigHash;
bytes3 color;