Skip to content

Commit

Permalink
feat: approval support - fungible (#195)
Browse files Browse the repository at this point in the history
Signed-off-by: Mariusz Jasuwienas <[email protected]>
  • Loading branch information
arianejasuwienas committed Jan 24, 2025
1 parent efd8eac commit 5253aaa
Showing 1 changed file with 61 additions and 27 deletions.
88 changes: 61 additions & 27 deletions contracts/HtsSystemContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,13 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {

function cryptoTransfer(TransferList memory transferList, TokenTransferList[] memory tokenTransfers)
htsCall external returns (int64 responseCode) {
_cryptoHbarTransfers(transferList);
_cryptoHbarTransfers(transferList.transfers);
for (uint256 tokenIndex = 0; tokenIndex < tokenTransfers.length; tokenIndex++) {
require(tokenTransfers[tokenIndex].token != address(0), "cryptoTransfer: invalid token");
_cryptoFungibleTransfers(
tokenTransfers[tokenIndex].token,
tokenTransfers[tokenIndex].transfers
);

uint256 validFungibleTransfersCount = 0;
for (uint256 ftIndex = 0; ftIndex < tokenTransfers[tokenIndex].transfers.length; ftIndex++) {
if (!tokenTransfers[tokenIndex].transfers[ftIndex].isApproval) {
Expand Down Expand Up @@ -927,36 +931,66 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
emit ApprovalForAll(sender, operator, approved);
}

function _cryptoHbarTransfers(TransferList memory transferList) internal {
int64 hbarBalance = 0;
for (uint256 i = 0; i < transferList.transfers.length; i++) {
hbarBalance += transferList.transfers[i].amount;
if (transferList.transfers[i].amount < 0) {
require(
transferList.transfers[i].isApproval || transferList.transfers[i].accountID == msg.sender,
"cryptoTransfer: invalid sender account"
function _cryptoHbarTransfers(AccountAmount[] memory transfers) internal {
_checkCryptoFungibleTransfers(address(0), transfers);
for (uint256 i = 0; i < transfers.length; i++) {
address account = transfers[i].isApproval ? transfers[i].accountID : msg.sender;
bool from = transfers[i].amount < 0;
uint256 amount = uint256(uint64(from ? -transfers[i].amount : transfers[i].amount));
_updateHbarBalanceOnAccount(account, from ? account.balance - amount : account.balance + amount);
if (from && account != msg.sender) _approve(from, msg.sender, allowance - amount);
}
}

function _cryptoFungibleTransfers(address token, AccountAmount[] memory transfers) internal {
require(token != address(0), "cryptoTransfer: invalid token");
_checkCryptoFungibleTransfers(token, transfers);
for (uint256 from = 0; from < transfers.length; from++) {
if (transfers[from].amount <= 0) {
continue;
}
for (uint256 to = 0; to < transfers.length; to++) {
if (transfers[to].amount <= 0) {
continue;
}
int64 transferAmount = transfers[to].amount < -transfers[from].amount ? transfers[to].amount : -transfers[from].amount;
transferToken(
token,
transfers[from].accountID,
transfers[to].accountID,
transferAmount
);
address from = transferList.transfers[i].isApproval ? transferList.transfers[i].accountID : msg.sender;
uint256 fromAmount = uint256(uint64(-transferList.transfers[i].amount));
require(fromAmount >= from.balance, "cryptoTransfer: insufficient balance");
uint256 allowance = 0;
if (msg.sender != from) {
allowance = __allowance(transferList.transfers[i].accountID, msg.sender);
require(allowance >= fromAmount, "cryptoTransfer: insufficient allowance");
transfers[from].amount += transferAmount;
transfers[to].amount -= transferAmount;
if (transfers[from].amount == 0) {
break;
}
_updateHbarBalanceOnAccount(from, from.balance - fromAmount);
if (allowance > 0) _approve(from, msg.sender, allowance - fromAmount);
}
if (transferList.transfers[i].amount > 0) {
uint256 toAmount = uint256(uint64(transferList.transfers[i].amount));
_updateHbarBalanceOnAccount(
transferList.transfers[i].accountID,
transferList.transfers[i].accountID.balance + toAmount
);
}
}

function _checkCryptoFungibleTransfers(address token, AccountAmount[] memory transfers) internal {
int64 total = 0;
mapping(address => int64) spends;
for (uint256 i = 0; i < transfers.length; i++) {
require(transfers[i].amount != 0, "cryptoTransfer: invalid amount");
total += transfers[i].amount;
if (transfers[i] > 0) {
continue;
}
spends[transfers[i].accountID] -= transfers[i].amount;
bool isApproval = transfers[i].accountID != msg.sender;
require(transfers[i].isApproval == isApproval, "cryptoTransfer: not allowed");
require(!isApproval || transfers[i].amount < 0, "cryptoTransfer: is approval option can only be used in the from part of the transaction");
if (isApproval) {
uint256 allowance = token == address(0) ?
__allowance(transfers[i].accountID, msg.sender) :
IERC20(token).allowance(transfers[i].accountID, msg.sender);
require(allowance >= spends[transfers[i].accountID], "cryptoTransfer: insufficient allowance");
}
revert("cryptoTransfer: invalid amount");
require(spends[transfers[i].accountID] <= transfers[i].accountID.balance, "cryptoTransfer: insufficient balance");
}
require(hbarBalance == 0, "cryptoTransfer: unmatched hbar transfers ");
require(total == 0, "cryptoTransfer: total amount must balance");
}

function _updateHbarBalanceOnAccount(address account, uint256 newBalance) internal virtual {
Expand Down

0 comments on commit 5253aaa

Please sign in to comment.