Skip to content

Commit

Permalink
feat: kyc wip; should be ok but requires fixing the test scenarios (#197
Browse files Browse the repository at this point in the history
)

Signed-off-by: Mariusz Jasuwienas <[email protected]>
  • Loading branch information
arianejasuwienas committed Jan 31, 2025
1 parent fbfc1a4 commit b9a2066
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 23 deletions.
122 changes: 108 additions & 14 deletions contracts/HtsSystemContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
_;
}

modifier kyc() {
address token = address(bytes20(msg.data[4:24]));
require(isKyc(token), "kyc: no kyc granted");
_;
}

/**
* @dev Returns the account id (omitting both shard and realm numbers) of the given `address`.
* The storage adapter, _i.e._, `getHtsStorageAt`, assumes that both shard and realm numbers are zero.
Expand Down Expand Up @@ -95,7 +101,7 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {

for (uint256 tokenIndex = 0; tokenIndex < tokenTransfers.length; tokenIndex++) {
require(tokenTransfers[tokenIndex].token != address(0), "cryptoTransfer: invalid token");

require(isKyc(tokenTransfers[tokenIndex].token), "cryptoTransfer: no kyc granted");
// Processing fungible token transfers
responseCode = _checkCryptoFungibleTransfers(tokenTransfers[tokenIndex].token, tokenTransfers[tokenIndex].transfers);
if (responseCode != HederaResponseCodes.SUCCESS) return responseCode;
Expand Down Expand Up @@ -164,7 +170,7 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
return HederaResponseCodes.SUCCESS;
}

function mintToken(address token, int64 amount, bytes[] memory) htsCall external returns (
function mintToken(address token, int64 amount, bytes[] memory) htsCall kyc external returns (
int64 responseCode,
int64 newTotalSupply,
int64[] memory serialNumbers
Expand All @@ -186,7 +192,7 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
require(newTotalSupply >= 0, "mintToken: invalid total supply");
}

function burnToken(address token, int64 amount, int64[] memory) htsCall external returns (
function burnToken(address token, int64 amount, int64[] memory) htsCall kyc external returns (
int64 responseCode,
int64 newTotalSupply
) {
Expand All @@ -213,6 +219,7 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
"associateTokens: Must be signed by the provided Account's key or called from the accounts contract key"
);
for (uint256 i = 0; i < tokens.length; i++) {
require(isKyc(tokens[i]), "associateTokens: no kyc granted");
require(tokens[i] != address(0), "associateTokens: invalid token");
int64 associationResponseCode = IHederaTokenService(tokens[i]).associateToken(account, tokens[i]);
require(
Expand All @@ -224,6 +231,7 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
}

function associateToken(address account, address token) htsCall external returns (int64 responseCode) {
require(isKyc(token), "associateToken: no kyc granted");
address[] memory tokens = new address[](1);
tokens[0] = token;
return associateTokens(account, tokens);
Expand All @@ -233,6 +241,7 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
require(tokens.length > 0, "dissociateTokens: missing tokens");
require(account == msg.sender, "dissociateTokens: Must be signed by the provided Account's key or called from the accounts contract key");
for (uint256 i = 0; i < tokens.length; i++) {
require(isKyc(tokens[i]), "dissociateTokens: no kyc granted");
require(tokens[i] != address(0), "dissociateTokens: invalid token");
int64 dissociationResponseCode = IHederaTokenService(tokens[i]).dissociateToken(account, tokens[i]);
require(dissociationResponseCode == HederaResponseCodes.SUCCESS, "dissociateTokens: Failed to dissociate token");
Expand All @@ -241,6 +250,7 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
}

function dissociateToken(address account, address token) htsCall external returns (int64 responseCode) {
require(isKyc(token), "dissociateToken: no kyc granted");
address[] memory tokens = new address[](1);
tokens[0] = token;
return dissociateTokens(account, tokens);
Expand All @@ -250,7 +260,7 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
address token,
address[] memory accountId,
int64[] memory amount
) htsCall external returns (int64 responseCode) {
) htsCall kyc external returns (int64 responseCode) {
require(token != address(0), "transferTokens: invalid token");
require(accountId.length > 0, "transferTokens: missing recipients");
require(amount.length == accountId.length, "transferTokens: inconsistent input");
Expand All @@ -266,7 +276,7 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
address[] memory sender,
address[] memory receiver,
int64[] memory serialNumber
) htsCall external returns (int64 responseCode) {
) htsCall kyc external returns (int64 responseCode) {
require(token != address(0), "transferNFTs: invalid token");
require(sender.length > 0, "transferNFTs: missing recipients");
require(receiver.length == sender.length, "transferNFTs: inconsistent input");
Expand All @@ -282,7 +292,7 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
address sender,
address recipient,
int64 amount
) htsCall public returns (int64 responseCode) {
) htsCall kyc public returns (int64 responseCode) {
require(token != address(0), "transferToken: invalid token");
address from = sender;
address to = recipient;
Expand All @@ -305,13 +315,13 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
address sender,
address recipient,
int64 serialNumber
) htsCall public returns (int64 responseCode) {
) htsCall kyc public returns (int64 responseCode) {
uint256 serialId = uint256(uint64(serialNumber));
HtsSystemContract(token).transferFromNFT(msg.sender, sender, recipient, serialId);
responseCode = HederaResponseCodes.SUCCESS;
}

function approve(address token, address spender, uint256 amount) htsCall public returns (int64 responseCode) {
function approve(address token, address spender, uint256 amount) htsCall kyc public returns (int64 responseCode) {
HtsSystemContract(token).approve(msg.sender, spender, amount);
responseCode = HederaResponseCodes.SUCCESS;
}
Expand All @@ -321,19 +331,19 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
address sender,
address recipient,
uint256 amount
) htsCall external returns (int64) {
) htsCall kyc external returns (int64) {
return transferToken(token, sender, recipient, int64(int256(amount)));
}

function allowance(address token, address owner, address spender) htsCall external view returns (int64, uint256) {
function allowance(address token, address owner, address spender) htsCall kyc external returns (int64, uint256) {
return (HederaResponseCodes.SUCCESS, IERC20(token).allowance(owner, spender));
}

function approveNFT(
address token,
address approved,
uint256 serialNumber
) htsCall public returns (int64 responseCode) {
) htsCall kyc public returns (int64 responseCode) {
HtsSystemContract(token).approveNFT(msg.sender, approved, serialNumber);
responseCode = HederaResponseCodes.SUCCESS;
}
Expand All @@ -343,7 +353,7 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
address from,
address to,
uint256 serialNumber
) htsCall external returns (int64) {
) htsCall kyc external returns (int64) {
return transferNFT(token, from, to, int64(int256(serialNumber)));
}

Expand All @@ -357,7 +367,7 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
address token,
address operator,
bool approved
) htsCall external returns (int64 responseCode) {
) htsCall kyc external returns (int64 responseCode) {
HtsSystemContract(token).setApprovalForAll(msg.sender, operator, approved);
responseCode = HederaResponseCodes.SUCCESS;
}
Expand All @@ -371,6 +381,17 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
return (HederaResponseCodes.SUCCESS, IERC721(token).isApprovedForAll(owner, operator));
}

function isKyc(address token, address account) htsCall public returns (int64, bool) {
if (!_hasKey(token, 0x2)) return (HederaResponseCodes.SUCCESS, true);
return IHederaTokenService(token).isKyc(msg.sender, account);
}

function isKyc(address token) htsCall internal returns (bool) {
if (!_hasKey(token, 0x2)) return true;
(, bool hasKyc) = isKyc(msg.sender, msg.sender);
return hasKyc;
}

function getTokenCustomFees(
address token
) htsCall external returns (int64, FixedFee[] memory, FractionalFee[] memory, RoyaltyFee[] memory) {
Expand Down Expand Up @@ -439,6 +460,16 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
return (responseCode, nonFungibleTokenInfo);
}

function grantTokenKyc(address token, address account) htsCall kyc external returns (int64 responseCode) {
require(_hasKey(token, 0x2), "grantTokenKyc: Only allowed for kyc tokens");
responseCode = IHederaTokenService(token).grantTokenKyc(token, account);
}

function revokeTokenKyc(address token, address account) htsCall kyc external returns (int64 responseCode) {
require(_hasKey(token, 0x2), "revokeTokenKyc: Only allowed for kyc tokens");
responseCode = IHederaTokenService(token).revokeTokenKyc(token, account);
}

function isToken(address token) htsCall external returns (int64, bool) {
bytes memory payload = abi.encodeWithSignature("getTokenType(address)", token);
(bool success, bytes memory returnData) = token.call(payload);
Expand Down Expand Up @@ -597,6 +628,11 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
_approve(from, to, serialId, true);
return abi.encode(true);
}
if (selector == this.isKyc.selector) {
require(msg.data.length >= 48, "associateToken: Not enough calldata");
address account = address(bytes20(msg.data[40:60]));
return abi.encode(HederaResponseCodes.SUCCESS, __hasKycGranted(account));
}
if (selector == this.setApprovalForAll.selector) {
require(msg.data.length >= 124, "setApprovalForAll: Not enough calldata");
address from = address(bytes20(msg.data[40:60]));
Expand All @@ -605,6 +641,18 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
_setApprovalForAll(from, to, approved);
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]));
_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]));
_updateKyc(account, false);
return abi.encode(HederaResponseCodes.SUCCESS);
}
if (selector == this._update.selector) {
require(msg.data.length >= 124, "update: Not enough calldata");
address from = address(bytes20(msg.data[40:60]));
Expand All @@ -615,6 +663,8 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
}
}

require(!_hasKey(0x2) || __hasKycGranted(msg.sender), "hts: no kyc granted");

// Redirect to the appropriate ERC20 method if the token type is fungible.
if (keccak256(bytes(tokenType)) == keccak256(bytes("FUNGIBLE_COMMON"))) {
return _redirectForERC20(selector);
Expand Down Expand Up @@ -820,6 +870,13 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
return bytes32(abi.encodePacked(selector, pad, ownerId, operatorId));
}

function _hasKycGrantedSlot(address account) internal virtual returns (bytes32) {
bytes4 selector = IHederaTokenService.isKyc.selector;
uint192 pad = 0x0;
uint32 accountId = HtsSystemContract(HTS_ADDRESS).getAccountId(account);
return bytes32(abi.encodePacked(selector, pad, accountId));
}

function __balanceOf(address account) private returns (uint256 amount) {
bytes32 slot = _balanceOfSlot(account);
assembly { amount := sload(slot) }
Expand Down Expand Up @@ -852,6 +909,11 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
assembly { approvedForAll := sload(slot) }
}

function __hasKycGranted(address account) private returns (bool hasKycGranted) {
bytes32 slot = _hasKycGrantedSlot(account);
assembly { hasKycGranted := sload(slot) }
}

function _transfer(address from, address to, uint256 amount) private {
require(from != address(0), "hts: invalid sender");
require(to != address(0), "hts: invalid receiver");
Expand All @@ -862,7 +924,6 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
function _transferNFT(address sender, address from, address to, uint256 serialId) private {
require(from != address(0), "hts: invalid sender");
require(to != address(0), "hts: invalid receiver");

// Check if the sender is the owner of the token
bytes32 slot = _ownerOfSlot(uint32(serialId));
address owner;
Expand All @@ -887,6 +948,11 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
emit Transfer(from, to, serialId);
}

function _updateKyc(address account, bool hasKycGranted) public {
bytes32 kycSlot = _hasKycGrantedSlot(account);
assembly { sstore(kycSlot, hasKycGranted) }
}

function _update(address from, address to, uint256 amount) public {
if (from == address(0)) {
totalSupply += amount;
Expand Down Expand Up @@ -1043,4 +1109,32 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {

return HederaResponseCodes.NOT_SUPPORTED;
}

function _hasKey(address token, uint keyType) htsCall private returns (bool hasKey) {
(hasKey, ) = _getKey(token, keyType);
}

function _hasKey(uint keyType) private returns (bool hasKey) {
(hasKey, ) = _getKey(keyType);
}

function _getKey(address token, uint keyType) htsCall private returns (bool, KeyValue memory) {
(int64 responseCode, TokenInfo memory tokenInfo) = getTokenInfo(token);
require(responseCode == 22, "_getKey: failed to access token info data");
return _getKey(keyType, tokenInfo);
}

function _getKey(uint keyType) private returns (bool, KeyValue memory) {
return _getKey(keyType, _tokenInfo);
}

function _getKey(uint keyType, TokenInfo memory tokenInfo) private returns (bool, KeyValue memory) {
for (uint256 i = 0; i < tokenInfo.token.tokenKeys.length; i++) {
if (tokenInfo.token.tokenKeys[i].keyType == keyType) {
return (true, tokenInfo.token.tokenKeys[i].key);
}
}
KeyValue memory value;
return (false, value);
}
}
18 changes: 9 additions & 9 deletions contracts/IHederaTokenService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -614,9 +614,9 @@ interface IHederaTokenService {
/// @param account The account address associated with the token
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return kycGranted True if `account` has kyc granted for `token`
// function isKyc(address token, address account)
// external
// returns (int64 responseCode, bool kycGranted);
function isKyc(address token, address account)
external
returns (int64 responseCode, bool kycGranted);

/// Operation to delete token
/// @param token The token address to be deleted
Expand Down Expand Up @@ -711,17 +711,17 @@ interface IHederaTokenService {
/// @param token The token address
/// @param account The account address to grant kyc
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
// function grantTokenKyc(address token, address account)
// external
// returns (int64 responseCode);
function grantTokenKyc(address token, address account)
external
returns (int64 responseCode);

/// Operation to revoke kyc to token account
/// @param token The token address
/// @param account The account address to revoke kyc
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
// function revokeTokenKyc(address token, address account)
// external
// returns (int64 responseCode);
function revokeTokenKyc(address token, address account)
external
returns (int64 responseCode);

/// Operation to pause token
/// @param token The token address to be paused
Expand Down

0 comments on commit b9a2066

Please sign in to comment.