Skip to content

Commit

Permalink
feat: token transfer associations (#151)
Browse files Browse the repository at this point in the history
Signed-off-by: Mariusz Jasuwienas <[email protected]>
  • Loading branch information
arianejasuwienas committed Jan 10, 2025
1 parent db6299d commit 51e2b42
Show file tree
Hide file tree
Showing 3 changed files with 235 additions and 95 deletions.
24 changes: 19 additions & 5 deletions contracts/HtsSystemContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
require(accountId.length > 0, "transferTokens: missing recipients");
require(amount.length == accountId.length, "transferTokens: inconsistent input");
for (uint256 i = 0; i < accountId.length; i++) {
require(accountId[i] != address(0) && accountId[i] != msg.sender, "transferTokens: Invalid accountId");
transferToken(token, msg.sender, accountId[i], amount[i]);
}
responseCode = 22; // HederaResponseCodes.SUCCESS
Expand All @@ -121,10 +120,9 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
to = sender;
amount *= -1;
}
require(IERC20(token).allowance(from, msg.sender));
HtsSystemContract(token)._update(from, to, uint256(uint64(amount)));

return 22; // HederaResponseCodes.SUCCESS
emit Transfer(from, to, uint256(uint64(amount)));
HtsSystemContract(token)._transferAsHTS(from, to, uint256(uint64(amount)));
responseCode = 22; // HederaResponseCodes.SUCCESS
}

function dissociateTokens(address account, address[] memory tokens) htsCall public returns (int64 responseCode) {
Expand Down Expand Up @@ -379,6 +377,14 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
}
return abi.encode(22, int32(-1));
}
if (selector == this._transferAsHTS.selector) {
require(msg.data.length >= 124, "update: Not enough calldata");
address from = address(bytes20(msg.data[40:60]));
address to = address(bytes20(msg.data[72:92]));
uint256 amount = uint256(bytes32(msg.data[92:124]));
_transferAsHTS(from, to, amount);
return abi.encode(true);
}
if (selector == this._update.selector) {
require(msg.data.length >= 124, "update: Not enough calldata");
address from = address(bytes20(msg.data[40:60]));
Expand Down Expand Up @@ -633,6 +639,14 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
emit Transfer(from, to, amount);
}

function _transferAsHTS(address from, address to, uint256 amount) public {
require(msg.sender == HTS_ADDRESS, "hts: not permitted");
require(from != address(0), "hts: invalid sender");
require(to != address(0), "hts: invalid receiver");
_update(from, to, amount);
emit Transfer(from, to, amount);
}

function _transferNFT(address from, address to, uint256 serialId) private {
require(from != address(0), "hts: invalid sender");
require(to != address(0), "hts: invalid receiver");
Expand Down
251 changes: 165 additions & 86 deletions out/HtsSystemContract.sol/HtsSystemContract.json

Large diffs are not rendered by default.

55 changes: 51 additions & 4 deletions test/HTS.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.0;
import {Test} from "forge-std/Test.sol";
import {HtsSystemContract, HTS_ADDRESS} from "../contracts/HtsSystemContract.sol";
import {IHederaTokenService} from "../contracts/IHederaTokenService.sol";
import {IERC20} from "../contracts/IERC20.sol";
import {IERC20Events, IERC20} from "../contracts/IERC20.sol";
import {IHRC719} from "../contracts/IHRC719.sol";
import {TestSetup} from "./lib/TestSetup.sol";

Expand Down Expand Up @@ -462,7 +462,7 @@ contract HTSTest is Test, TestSetup {

function test_HTS_associations_with_correct_privileges() external {
address bob = CFNFTFF_TREASURY;
vm.startPrank(bob);
vm.startPrank(bob); // https://book.getfoundry.sh/cheatcodes/prank
assertFalse(IHRC719(USDC).isAssociated());

// Associate the token.
Expand All @@ -472,7 +472,7 @@ contract HTSTest is Test, TestSetup {

// Dissociate this token.
int64 dissociationResponseCode = HtsSystemContract(HTS_ADDRESS).dissociateToken(bob, USDC);
assertEq(associationResponseCode, 22);
assertEq(dissociationResponseCode, 22);
assertFalse(IHRC719(USDC).isAssociated());

// Associate multiple tokens at once.
Expand All @@ -482,7 +482,7 @@ contract HTSTest is Test, TestSetup {
tokens[0] = USDC;
tokens[1] = MFCT;
int64 multiAssociateResponseCode = HtsSystemContract(HTS_ADDRESS).associateTokens(bob, tokens);
assertEq(associationResponseCode, 22);
assertEq(multiAssociateResponseCode, 22);
assertTrue(IHRC719(USDC).isAssociated());
assertTrue(IHRC719(MFCT).isAssociated());

Expand Down Expand Up @@ -611,4 +611,51 @@ contract HTSTest is Test, TestSetup {
assertEq(nonFungibleTokenInfo.tokenInfo.royaltyFees.length, 0);
assertEq(nonFungibleTokenInfo.tokenInfo.ledgerId, testMode == TestMode.FFI ? "0x01" : "0x00");
}

function test_HTS_transferToken() external {
// https://hashscan.io/testnet/account/0.0.1421
address owner = 0x4D1c823b5f15bE83FDf5adAF137c2a9e0E78fE15;
address to = makeAddr("bob");
uint256 amount = 4_000000;

uint256 balanceOfOwner = IERC20(USDC).balanceOf(owner);
assertGt(balanceOfOwner, 0);
assertEq(IERC20(USDC).balanceOf(to), 0);

vm.prank(owner); // https://book.getfoundry.sh/cheatcodes/prank
vm.expectEmit(USDC);
emit IERC20Events.Transfer(owner, to, amount);
IHederaTokenService(HTS_ADDRESS).transferToken(USDC, owner, to, int64(int256(amount)));

assertEq(IERC20(USDC).balanceOf(owner), balanceOfOwner - amount);
assertEq(IERC20(USDC).balanceOf(to), amount);
}

function test_HTS_transferTokens() external {
// https://hashscan.io/testnet/account/0.0.1421
address owner = 0x4D1c823b5f15bE83FDf5adAF137c2a9e0E78fE15;
uint256 amountToBob = 1_000000;
uint256 amountToAlice = 3_000000;
int64[] memory amount = new int64[](2);
amount[0] = int64(int256(1_000000));
amount[1] = int64(int256(amountToAlice));
address[] memory to = new address[](2);
to[0] = makeAddr("bob");
to[1] = makeAddr("alice");
address[] memory from = new address[](2);
to[0] = makeAddr("bob");
to[1] = makeAddr("alice");
uint256 balanceOfOwner = IERC20(USDC).balanceOf(owner);
assertGt(balanceOfOwner, 0);
assertEq(IERC20(USDC).balanceOf(to[0]), 0);
assertEq(IERC20(USDC).balanceOf(to[1]), 0);
vm.prank(owner); // https://book.getfoundry.sh/cheatcodes/prank
vm.expectEmit(USDC);
emit IERC20Events.Transfer(owner, to[0], amountToBob);
emit IERC20Events.Transfer(owner, to[1], amountToAlice);
IHederaTokenService(HTS_ADDRESS).transferTokens(USDC, to, amount);
assertEq(IERC20(USDC).balanceOf(owner), balanceOfOwner - amountToBob - amountToAlice);
assertEq(IERC20(USDC).balanceOf(to[0]), amountToBob);
assertEq(IERC20(USDC).balanceOf(to[1]), amountToAlice);
}
}

0 comments on commit 51e2b42

Please sign in to comment.