From 30e486ade21afb1d1bf8c3e121e228096af3187e Mon Sep 17 00:00:00 2001 From: Mariusz Jasuwienas Date: Thu, 23 Jan 2025 14:09:48 +0100 Subject: [PATCH] feat: use nonpayable method for hbar transfers as suggested (#195) Signed-off-by: Mariusz Jasuwienas --- contracts/HtsSystemContract.sol | 27 ++++-------------- contracts/HtsSystemContractJson.sol | 31 +++++++++++++++++++++ contracts/IHederaTokenService.sol | 2 +- test/HTS.t.sol | 43 +++++------------------------ 4 files changed, 45 insertions(+), 58 deletions(-) diff --git a/contracts/HtsSystemContract.sol b/contracts/HtsSystemContract.sol index a6695de..12ee1ae 100644 --- a/contracts/HtsSystemContract.sol +++ b/contracts/HtsSystemContract.sol @@ -44,27 +44,8 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events { } function cryptoTransfer(TransferList memory transferList, TokenTransferList[] memory tokenTransfers) - payable htsCall external returns (int64 responseCode) { - uint256 hbarsReceived = msg.value; - int64 hbarBalance = 0; - for (uint256 hbarIndex = 0; hbarIndex < transferList.transfers.length; hbarIndex++) { - require(!transferList.transfers[hbarIndex].isApproval, "cryptoTransfer: hbar approval is not supported"); - hbarBalance += transferList.transfers[hbarIndex].amount; - if (transferList.transfers[hbarIndex].amount < 0) { - require( - transferList.transfers[hbarIndex].accountID == msg.sender, - "cryptoTransfer: hbar transfer allowed only from the msg sender account" - ); - continue; - } - require(transferList.transfers[hbarIndex].amount > 0, "cryptoTransfer: invalid amount"); - uint256 value = uint256(uint64(transferList.transfers[hbarIndex].amount)); - require(hbarsReceived >= value, "cryptoTransfer: insufficient balance"); - hbarsReceived -= value; - (bool success, ) = transferList.transfers[hbarIndex].accountID.call{value: value}(""); - require(success, "cryptoTransfer: hbar transfer failure"); - } - require(hbarBalance == 0 && hbarsReceived == 0, "cryptoTransfer: unmatched hbar transfers "); + htsCall external returns (int64 responseCode) { + _cryptoHbarTransfer(transferList); for (uint256 tokenIndex = 0; tokenIndex < tokenTransfers.length; tokenIndex++) { require(tokenTransfers[tokenIndex].token != address(0), "cryptoTransfer: invalid token"); uint256 validFungibleTransfersCount = 0; @@ -945,4 +926,8 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events { assembly { sstore(slot, approved) } emit ApprovalForAll(sender, operator, approved); } + + function _cryptoHbarTransfer(TransferList memory transferList) internal virtual { + require(transferList.transfers.length == 0, "cryptoTransfer: hbar transfer is not supported"); + } } diff --git a/contracts/HtsSystemContractJson.sol b/contracts/HtsSystemContractJson.sol index 35186a7..2797f5a 100644 --- a/contracts/HtsSystemContractJson.sol +++ b/contracts/HtsSystemContractJson.sol @@ -473,4 +473,35 @@ contract HtsSystemContractJson is HtsSystemContract { function _scratchAddr() private view returns (address) { return address(bytes20(keccak256(abi.encode(address(this))))); } + + function _cryptoHbarTransfer(TransferList memory transferList) internal override { + int64 hbarBalance = 0; + for (uint256 hbarIndex = 0; hbarIndex < transferList.transfers.length; hbarIndex++) { + require(transferList.transfers[hbarIndex].amount != 0, "cryptoTransfer: invalid amount"); + require(!transferList.transfers[hbarIndex].isApproval, "cryptoTransfer: hbar approval is not supported"); + hbarBalance += transferList.transfers[hbarIndex].amount; + if (transferList.transfers[hbarIndex].amount < 0) { + require( + transferList.transfers[hbarIndex].accountID == msg.sender, + "cryptoTransfer: hbar transfer allowed only from the msg sender account, approved transfers are not supported" + ); + uint256 fromAmount = uint256(uint64(-transferList.transfers[hbarIndex].amount)); + require( + fromAmount <= transferList.transfers[hbarIndex].accountID.balance, + "cryptoTransfer: insufficient balance" + ); + vm.deal( + transferList.transfers[hbarIndex].accountID, + transferList.transfers[hbarIndex].accountID.balance - fromAmount + ); + continue; + } + uint256 toAmount = uint256(uint64(transferList.transfers[hbarIndex].amount)); + vm.deal( + transferList.transfers[hbarIndex].accountID, + transferList.transfers[hbarIndex].accountID.balance + toAmount + ); + } + require(hbarBalance == 0, "cryptoTransfer: unmatched hbar transfers "); + } } diff --git a/contracts/IHederaTokenService.sol b/contracts/IHederaTokenService.sol index b85fd2d..bceac7a 100644 --- a/contracts/IHederaTokenService.sol +++ b/contracts/IHederaTokenService.sol @@ -305,7 +305,7 @@ interface IHederaTokenService { /// @param tokenTransfers the list of token transfers to do /// @custom:version 0.3.0 the signature of the previous version was cryptoTransfer(TokenTransferList[] memory tokenTransfers) function cryptoTransfer(TransferList memory transferList, TokenTransferList[] memory tokenTransfers) - external payable + external returns (int64 responseCode); /// Mints an amount of the token to the defined treasury account diff --git a/test/HTS.t.sol b/test/HTS.t.sol index d9519db..4ef5b72 100644 --- a/test/HTS.t.sol +++ b/test/HTS.t.sol @@ -915,9 +915,7 @@ contract HTSTest is Test, TestSetup { assertEq(initialRecipient1Balance, 0); assertEq(initialRecipient2Balance, 0); vm.prank(owner); - vm.expectCall(recipient1, hbarToRecipient1, ""); - vm.expectCall(recipient2, hbarToRecipient2, ""); - int64 code = IHederaTokenService(HTS_ADDRESS).cryptoTransfer{value: hbarToRecipient1 + hbarToRecipient2}( + int64 code = IHederaTokenService(HTS_ADDRESS).cryptoTransfer( transferList, new IHederaTokenService.TokenTransferList[](0) ); @@ -948,18 +946,19 @@ contract HTSTest is Test, TestSetup { transferList.transfers[1] = transfer2; vm.deal(owner, hbarToRecipient); vm.deal(sender, hbarToRecipient); - vm.expectRevert("cryptoTransfer: hbar transfer allowed only from the msg sender account"); + vm.expectRevert("cryptoTransfer: hbar transfer allowed only from the msg sender account, approved transfers are not supported"); vm.prank(owner); - IHederaTokenService(HTS_ADDRESS).cryptoTransfer{value: hbarToRecipient}( + IHederaTokenService(HTS_ADDRESS).cryptoTransfer( transferList, new IHederaTokenService.TokenTransferList[](0) ); } - function test_HTS_cryptoTransfer_test_reject_insufficient_value_send() external { + function test_HTS_cryptoTransfer_test_reject_insufficient_balance() external { address owner = 0x4D1c823b5f15bE83FDf5adAF137c2a9e0E78fE15; address recipient = makeAddr("recipient"); uint256 hbarToRecipient = 1 ether; + vm.deal(owner, 0.5 ether); // Not enough IHederaTokenService.AccountAmount memory transfer1 = IHederaTokenService.AccountAmount( owner, -int64(uint64(hbarToRecipient)), @@ -974,10 +973,9 @@ contract HTSTest is Test, TestSetup { transferList.transfers = new IHederaTokenService.AccountAmount[](2); transferList.transfers[0] = transfer1; transferList.transfers[1] = transfer2; - vm.deal(owner, hbarToRecipient); vm.expectRevert("cryptoTransfer: insufficient balance"); vm.prank(owner); - IHederaTokenService(HTS_ADDRESS).cryptoTransfer{value: hbarToRecipient - 0.5 ether}( + IHederaTokenService(HTS_ADDRESS).cryptoTransfer( transferList, new IHederaTokenService.TokenTransferList[](0) ); @@ -1004,34 +1002,7 @@ contract HTSTest is Test, TestSetup { vm.deal(owner, hbarToRecipient); vm.expectRevert("cryptoTransfer: hbar approval is not supported"); vm.prank(owner); - IHederaTokenService(HTS_ADDRESS).cryptoTransfer{value: hbarToRecipient}( - transferList, - new IHederaTokenService.TokenTransferList[](0) - ); - } - - function test_HTS_cryptoTransfer_test_reject_wrong_value() external { - address owner = 0x4D1c823b5f15bE83FDf5adAF137c2a9e0E78fE15; - address recipient = makeAddr("recipient"); - uint256 hbarToRecipient = 1 ether; - IHederaTokenService.AccountAmount memory transfer1 = IHederaTokenService.AccountAmount( - owner, - -int64(uint64(hbarToRecipient)), - false - ); - IHederaTokenService.AccountAmount memory transfer2 = IHederaTokenService.AccountAmount( - recipient, - int64(uint64(hbarToRecipient)), - false - ); - IHederaTokenService.TransferList memory transferList; - transferList.transfers = new IHederaTokenService.AccountAmount[](2); - transferList.transfers[0] = transfer1; - transferList.transfers[1] = transfer2; - vm.deal(owner, hbarToRecipient); - vm.expectRevert(); - vm.prank(owner); - IHederaTokenService(HTS_ADDRESS).cryptoTransfer{value: hbarToRecipient + 0.5 ether}( + IHederaTokenService(HTS_ADDRESS).cryptoTransfer( transferList, new IHederaTokenService.TokenTransferList[](0) );