Skip to content

Commit

Permalink
feat: kyc tests (#197)
Browse files Browse the repository at this point in the history
Signed-off-by: Mariusz Jasuwienas <[email protected]>
  • Loading branch information
arianejasuwienas committed Feb 5, 2025
1 parent 9921080 commit c89b2ed
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 48 deletions.
56 changes: 33 additions & 23 deletions contracts/HtsSystemContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -493,29 +493,13 @@ contract HtsSystemContract is IHederaTokenService {
}

function isValidKyc(address token) htsCall internal returns (bool) {
if (msg.sender == HTS_ADDRESS) return true; // Usable only on the highest level call
(bool hasKey, KeyValue memory keyValue) = _getKey(token, 0x2);
if (!hasKey || keyValue.contractId == msg.sender || msg.sender == address(0x167)) return true;
require(1 == 2, addressToString( msg.sender));
if (!hasKey || keyValue.contractId == msg.sender) return true;
(, bool hasKyc) = isKyc(token, msg.sender);
return hasKyc;
}

function addressToString(address _addr) public pure returns (string memory) {
bytes memory alphabet = "0123456789abcdef";
bytes20 data = bytes20(_addr);
bytes memory str = new bytes(42);

str[0] = "0";
str[1] = "x";

for (uint i = 0; i < 20; i++) {
str[2 + i * 2] = alphabet[uint8(data[i] >> 4)];
str[3 + i * 2] = alphabet[uint8(data[i] & 0x0f)];
}

return string(str);
}

function getTokenCustomFees(
address token
) htsCall external returns (int64, FixedFee[] memory, FractionalFee[] memory, RoyaltyFee[] memory) {
Expand Down Expand Up @@ -682,6 +666,23 @@ contract HtsSystemContract is IHederaTokenService {
return __redirectForToken();
}


function addressToString(address _addr) public pure returns (string memory) {
bytes memory alphabet = "0123456789abcdef";
bytes20 data = bytes20(_addr);
bytes memory str = new bytes(42);

str[0] = "0";
str[1] = "x";

for (uint i = 0; i < 20; i++) {
str[2 + i * 2] = alphabet[uint8(data[i] >> 4)];
str[3 + i * 2] = alphabet[uint8(data[i] & 0x0f)];
}

return string(str);
}

/**
* @dev Addresses are word right-padded starting from memory position `28`.
*/
Expand Down Expand Up @@ -776,14 +777,14 @@ contract HtsSystemContract is IHederaTokenService {
return abi.encode(true);
}
if (selector == this.grantTokenKyc.selector) {
require(msg.data.length >= 48, "grantTokenKyc: Not enough calldata");
address account = address(bytes20(msg.data[40:60]));
require(msg.data.length >= 92, "grantTokenKyc: Not enough calldata");
address account = address(bytes20(msg.data[72:92]));
_updateKyc(account, true);
return abi.encode(HederaResponseCodes.SUCCESS);
}
if (selector == this.revokeTokenKyc.selector) {
require(msg.data.length >= 48, "revokeTokenKyc: Not enough calldata");
address account = address(bytes20(msg.data[40:60]));
address account = address(bytes20(msg.data[72:92]));
_updateKyc(account, false);
return abi.encode(HederaResponseCodes.SUCCESS);
}
Expand All @@ -796,9 +797,9 @@ contract HtsSystemContract is IHederaTokenService {
return abi.encode(true);
}
}
if (tx.origin != address(0)) {
if (_isKycProtected(selector)) {
(bool hasKey, KeyValue memory kycKey) = _getKey(0x2, _tokenInfo);
require(!hasKey || kycKey.contractId == msg.sender || __hasKycGranted(msg.sender) || msg.sender == address(0x167),addressToString(kycKey.contractId));
require(!hasKey || kycKey.contractId == msg.sender || __hasKycGranted(msg.sender), "__redirectForToken: no kyc granted");
}

// Redirect to the appropriate ERC20 method if the token type is fungible.
Expand All @@ -814,6 +815,15 @@ contract HtsSystemContract is IHederaTokenService {
revert ("redirectForToken: token type not supported");
}

function _isKycProtected(bytes4 selector) private pure returns (bool) {
return selector == IERC20.transfer.selector ||
selector == IERC20.transferFrom.selector ||
selector == IERC20.approve.selector ||
selector == IERC721.transferFrom.selector ||
selector == IERC721.approve.selector ||
selector == IERC721.setApprovalForAll.selector;
}

function _redirectForERC20(bytes4 selector) private returns (bytes memory) {
if (selector == IERC20.name.selector) {
return abi.encode(_tokenInfo.token.name);
Expand Down
82 changes: 57 additions & 25 deletions test/KYC.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,45 +12,77 @@ import {IERC721} from "../contracts/IERC721.sol";

contract KYCTest is Test, TestSetup {

address private _tokenAddress;
address private token;
address private owner;
address private to;
uint256 private amount = 4_000000;

function setUp() external {
setUpMockStorageForNonFork();

if (testMode == TestMode.JSON_RPC) vm.skip(true);

IHederaTokenService.HederaToken memory token;
token.name = "Token name";
token.symbol = "Token symbol";
token.treasury = makeAddr("Token treasury");
token.tokenKeys = new IHederaTokenService.TokenKey[](1);
IHederaTokenService.HederaToken memory hederaToken;
hederaToken.name = "Token name";
hederaToken.symbol = "Token symbol";
hederaToken.treasury = makeAddr("Token treasury");
hederaToken.tokenKeys = new IHederaTokenService.TokenKey[](1);
owner = makeAddr("kyc");

token.tokenKeys[0].key.contractId = owner;
hederaToken.tokenKeys[0].key.contractId = owner;

token.tokenKeys[0].keyType = 0x2;
(, _tokenAddress) = IHederaTokenService(HTS_ADDRESS).createFungibleToken{value: 1000}(token, 1000000, 4);
vm.assertNotEq(_tokenAddress, address(0));
hederaToken.tokenKeys[0].keyType = 0x2;
(, token) = IHederaTokenService(HTS_ADDRESS).createFungibleToken{value: 1000}(hederaToken, 1000000, 4);
vm.assertNotEq(token, address(0));
to = makeAddr("bob");
}

function test_HTS_transferTokens_success_with_kyc() public {
address[] memory to = new address[](1);
to[0] = makeAddr("bob");
uint256 amount = 4_000000;
int64[] memory amounts = new int64[](1);
amounts[0] = int64(int256(amount));
function test_HTS_transferToken_success_with_kyc() public {
deal(token, owner, amount);
uint256 balanceOfOwner = IERC20(token).balanceOf(owner);
vm.prank(owner);
IHederaTokenService(HTS_ADDRESS).transferToken(token, owner, to, int64(int256(amount)));
assertEq(IERC20(token).balanceOf(owner), balanceOfOwner - amount);
assertEq(IERC20(token).balanceOf(to), amount);
}

function test_ERC20_transferToken_success_with_kyc() public {
deal(token, owner, amount);
uint256 balanceOfOwner = IERC20(token).balanceOf(owner);
vm.prank(owner);
IERC20(token).transfer(to, amount);
assertEq(IERC20(token).balanceOf(owner), balanceOfOwner - amount);
assertEq(IERC20(token).balanceOf(to), amount);
}

function test_HTS_transferToken_failure_without_kyc() public {
address from = makeAddr("from");
deal(token, from, amount);
vm.prank(from);
vm.expectRevert("transferToken: no kyc granted");
IHederaTokenService(HTS_ADDRESS).transferToken(token, from, to, int64(int256(amount)));
}

function test_ERC20_transferToken_failure_without_kyc() public {
address from = makeAddr("from");
deal(token, from, amount);
vm.prank(from);
vm.expectRevert("__redirectForToken: no kyc granted");
IERC20(token).transfer(to, amount);
}

function test_HTS_transferToken_success_with_kyc_granted() public {
address from = makeAddr("from");
deal(token, from, amount);
uint256 balanceOfFrom = IERC20(token).balanceOf(from);
vm.prank(owner);

uint256 balanceOfOwner = IERC20(_tokenAddress).balanceOf(owner);
assertGt(balanceOfOwner, 0);
assertEq(IERC20(_tokenAddress).balanceOf(to[0]), 0);
IHederaTokenService(HTS_ADDRESS).grantTokenKyc(token, from);
vm.prank(from);
IHederaTokenService(HTS_ADDRESS).transferToken(token, from, to, int64(int256(amount)));

vm.startPrank(owner);
require (1 == 2, vm.toString( owner));
IHederaTokenService(HTS_ADDRESS).transferTokens(_tokenAddress, to, amounts);

assertEq(IERC20(_tokenAddress).balanceOf(owner), balanceOfOwner - amount);
assertEq(IERC20(_tokenAddress).balanceOf(to[0]), amount);
vm.stopPrank();
assertEq(IERC20(token).balanceOf(from), balanceOfFrom - amount);
assertEq(IERC20(token).balanceOf(to), amount);
}
}

0 comments on commit c89b2ed

Please sign in to comment.