Skip to content

Commit

Permalink
feat: implement missing ihts facade methods (#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 8, 2025
1 parent a01e9f7 commit 53dd17f
Show file tree
Hide file tree
Showing 3 changed files with 403 additions and 140 deletions.
264 changes: 264 additions & 0 deletions contracts/HtsSystemContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {IERC20Events, IERC20} from "./IERC20.sol";
import {IERC721, IERC721Events} from "./IERC721.sol";
import {IHRC719} from "./IHRC719.sol";
import {IHederaTokenService} from "./IHederaTokenService.sol";
import {IDestructible} from "./IDestructible.sol";

address constant HTS_ADDRESS = address(0x167);

Expand Down Expand Up @@ -48,6 +49,262 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
(responseCode, tokenInfo) = IHederaTokenService(token).getTokenInfo(token);
}

function associateTokens(address account, address[] memory tokens) htsCall external returns (int64 responseCode) {
require(tokens.length > 0, "dissociateTokens: missing tokens");
require(account == msg.sender, "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(tokens[i] != address(0), "associateTokens: invalid token");
IHRC719(tokens[i]).associate();
}
responseCode = 22; // HederaResponseCodes.SUCCESS
}

function associateToken(address account, address token) htsCall external returns (int64 responseCode) {
require(account == msg.sender, "associateToken: Must be signed by the provided Account's key or called from the accounts contract key");
IHRC719(token).associate();
responseCode = 22; // HederaResponseCodes.SUCCESS
}

function dissociateTokens(address account, address[] memory tokens) htsCall external returns (int64 responseCode) {
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(tokens[i] != address(0), "dissociateTokens: invalid token");
IHRC719(tokens[i]).dissociate();
}
responseCode = 22; // HederaResponseCodes.SUCCESS
}

function dissociateToken(address account, address token) htsCall external returns (int64 responseCode) {
require(token != address(0), "transferTokens: invalid token");
require(account == msg.sender, "dissociateToken: Must be signed by the provided Account's key or called from the accounts contract key");
IHRC719(token).dissociate();
responseCode = 22; // HederaResponseCodes.SUCCESS
}

function transferTokens(
address token,
address[] memory accountId,
int64[] memory amount
) htsCall external returns (int64 responseCode) {
require(token != address(0), "transferTokens: invalid token");
require(accountId.length > 0, "transferTokens: missing recipients");
require(amount.length != accountId.length, "transferTokens: invalid input");
for (uint256 i = 0; i < accountId.length; i++) {
require(amount[i] > 0, "transferTokens: invalid amount");
IERC20(token).transfer(accountId[i], amount[i]);
}
responseCode = 22; // HederaResponseCodes.SUCCESS
}

function transferNFTs(
address token,
address[] memory sender,
address[] memory receiver,
int64[] memory serialNumber
) htsCall external returns (int64 responseCode) {
require(token != address(0), "token: invalid token");
require(sender.length == receiver.length, "transferNFTs: invalid input");
require(sender.length == serialNumber.length, "transferNFTs: invalid input");
for (uint256 i = 0; i < sender.length; i++) {
IERC721(token).transferFrom(sender[i], receiver[i], serialNumber[i]);
}
responseCode = 22; // HederaResponseCodes.SUCCESS
}

function transferToken(
address token,
address sender,
address recipient,
int64 amount
) htsCall external returns (int64 responseCode) {
require(token != address(0), "transferToken: invalid token");
require(amount > 0, "transferToken: invalid amount");
IERC20(token).transferFrom(sender, recipient, amount);
responseCode = 22; // HederaResponseCodes.SUCCESS
}

function transferNFT(
address token,
address sender,
address recipient,
int64 serialNumber
) htsCall external returns (int64 responseCode) {
require(token != address(0), "transferNFT: invalid token");
IERC721(token).transferFrom(sender, recipient, serialNumber);
responseCode = 22; // HederaResponseCodes.SUCCESS
}

function approve(
address token,
address spender,
uint256 amount
) htsCall external returns (int64 responseCode) {
require(token != address(0), "approve: invalid token");
IERC20(token).approve(spender, amount);
responseCode = 22; // HederaResponseCodes.SUCCESS
}

function transferFrom(address token, address from, address to, uint256 amount)
htsCall external returns (int64 responseCode) {
require(token != address(0), "transferFrom: invalid token");
IERC721(token).transferFrom(from, to, amount);
responseCode = 22; // HederaResponseCodes.SUCCESS
}

function allowance(
address token,
address owner,
address spender
) htsCall external returns (int64 responseCode, uint256 allowance) {
require(token != address(0), "allowance: invalid token");
allowance = IERC20(token).allowance(owner, spender);
responseCode = 22; // HederaResponseCodes.SUCCESS
}

function approveNFT(
address token,
address approved,
uint256 serialNumber
) htsCall external returns (int64 responseCode) {
require(token != address(0), "approveNFT: invalid token");
IERC721(token).approve(approved, serialNumber);
responseCode = 22; // HederaResponseCodes.SUCCESS
}

function transferFromNFT(address token, address from, address to, uint256 serialNumber)
htsCall external returns (int64 responseCode) {
require(token != address(0), "transferFromNFT: invalid token");
IERC721(token).transferFrom(from, to, serialNumber);
responseCode = 22; // HederaResponseCodes.SUCCESS
}

function getApproved(address token, uint256 serialNumber)
htsCall external returns (int64 responseCode, address approved) {
require(token != address(0), "getApproved: invalid token");
(responseCode, approved) = (int64(22), IERC721(token).getApproved(serialNumber));
}

function setApprovalForAll(
address token,
address operator,
bool approved
) htsCall external returns (int64 responseCode) {
require(token != address(0), "setApprovalForAll: invalid token");
IERC721(token).setApprovalForAll(operator, approved);
responseCode = 22; // HederaResponseCodes.SUCCESS
}

function isApprovedForAll(
address token,
address owner,
address operator
) htsCall external returns (int64 responseCode, bool approved) {
require(token != address(0), "isApprovedForAll: invalid token");
(responseCode, approved) = (int64(22), IERC721(token).isApprovedForAll(owner, approved));
}

function deleteToken(address token) htsCall external returns (int64 responseCode) {
require(token != address(0), "deleteToken: invalid token");
(int64 responseCode, TokenInfo memory tokenInfo) = IHederaTokenService(address(this)).getTokenInfo(address(this));
require(responseCode == 22, "destroy: only allowed for tokens");
KeyValue memory adminKey;
for (uint256 i = 0; i < tokenInfo.token.tokenKeys.length; i++) {
if (tokenInfo.token.tokenKeys[i].keyType == 128) {
adminKey = tokenInfo.token.tokenKeys[i].key;
}
}
require(adminKey.ECDSA_secp256k1 != "" || adminKey.ed25519 != "", "destroy: no admin key");
require(
adminKey.contractId == msg.sender ||
adminKey.delegatableContractId == msg.sender ||
address(uint160(uint256(keccak256(adminKey.ed25519)))) == msg.sender ||
address(uint160(uint256(keccak256(adminKey.ECDSA_secp256k1)))) == msg.sender,
"destroy: only admin key is allowed to perform this operation"
);
IHederaTokenService(token).deleteToken(token);
responseCode = 22; // HederaResponseCodes.SUCCESS
}

function cryptoTransfer(TransferList memory transferList, TokenTransferList[] memory tokenTransfers)
external
returns (int64 responseCode) {
require(TransferList.transfers.length + tokenTransfers.length > 0, "cryptoTransfer: Empty transfer lists");
AccountAmount memory accountAmount;
NftTransfer memory nftTransfer;
for (uint256 hbarTransferIndex = 0; hbarTransferIndex < transferList.transfers.length; hbarTransferIndex++) {
accountAmount = transferList.transfers[hbarTransferIndex];
require(accountAmount.amount > 0, "cryptoTransfer: invalid amount");
require(accountAmount.accountID != address(0), "cryptoTransfer: invalid account");
if (accountAmount.isApproval) {
revert("cryptoTransfer: Approve to spend hbar operation is not supported");
} else {
payable(accountAmount.amount).call{value: accountAmount.amount}("");
}
}
address tokenAddress;
for (uint256 tokenTransferIndex = 0; tokenTransferIndex < tokenTransfers.length; tokenTransferIndex++) {
tokenAddress = tokenTransfers[tokenTransferIndex].token;
require(accountAmount.amount > 0, "cryptoTransfer: invalid amount");
require(tokenAddress != address(0), "cryptoTransfer: invalid token");
for (uint256 ftIndex = 0; ftIndex < tokenTransfers[tokenTransferIndex].transfers.length; ftIndex++) {
accountAmount = tokenTransfers[ftIndex].transfers[ftIndex];
require(accountAmount.amount > 0, "cryptoTransfer: invalid amount");
require(accountAmount.accountID != address(0), "cryptoTransfer: invalid account");
if (accountAmount.isApproval) {
IERC20(tokenAddress).approve(accountAmount.accountID, accountAmount.amount);
} else {
IERC20(tokenAddress).transfer(accountAmount.accountID, accountAmount.amount);
}
}
for (uint256 nftIndex = 0; nftIndex < tokenTransfers[tokenTransferIndex].transfers.length; nftIndex++) {
nftTransfer = tokenTransfers[tokenTransferIndex].nftTransfers[nftIndex];
require(nftTransfer.receiverAccountID != address(0), "cryptoTransfer: invalid receiver");
if (accountAmount.isApproval) {
IERC721(tokenAddress).approve(nftTransfer.receiverAccountID, nftTransfer.serialNumber);
} else {
require(nftTransfer.senderAccountID != address(0), "cryptoTransfer: invalid sender");
IERC721(tokenAddress).transferFrom(nftTransfer.senderAccountID, nftTransfer.receiverAccountID, nftTransfer.serialNumber);
}
}
}
responseCode = 22; // HederaResponseCodes.SUCCESS
}

function isToken(address token) htsCall external returns (int64 responseCode, bool isToken) {
require(token != address(0), "isToken: invalid address");
(responseCode, _tokenInfo) = IHederaTokenService(token).getTokenInfo(token);
isToken = responseCode == 22;
}

function getTokenType(address token) htsCall external returns (int64 responseCode, int32 tokenType) {
require(token != address(0), "getTokenType: invalid token");
(responseCode, tokenType) = IHederaTokenService(token).getTokenType(token);
}

function getTokenDefaultKycStatus(address token)
htsCall external returns (int64 responseCode, bool defaultKycStatus) {
(responseCode, _tokenInfo) = IHederaTokenService.getTokenInfo(token);
require(responseCode == 22, "getTokenDefaultKycStatus: failure");
defaultKycStatus = _tokenInfo.defaultKycStatus;
}

function getTokenDefaultFreezeStatus(address token)
htsCall external returns (int64 responseCode, bool defaultFreezeStatus) {
(responseCode, _tokenInfo) = IHederaTokenService.getTokenInfo(token);
require(responseCode == 22, "getTokenDefaultFreezeStatus: failure");
defaultFreezeStatus = _tokenInfo.token.freezeDefault;
}

function getTokenCustomFees(address token) htsCall external
returns (int64 responseCode, FixedFee[] memory fixedFees, FractionalFee[] memory fractionalFees, RoyaltyFee[] memory royaltyFees) {
require(token != address(0), "getTokenType: invalid token");
(responseCode, _tokenInfo) = IHederaTokenService(token).getTokenType(token);
fixedFees = _tokenInfo.fixedFees;
fractionalFees = _tokenInfo.fractionalFees;
royaltyFees = _tokenInfo.royaltyFees;
}

function mintToken(address token, int64 amount, bytes[] memory) htsCall external returns (
int64 responseCode,
int64 newTotalSupply,
Expand Down Expand Up @@ -175,6 +432,13 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events {
require(msg.data.length >= 28, "getTokenInfo: Not enough calldata");
return abi.encode(22, _tokenInfo);
}
if (selector == this.getTokenType.selector) {
require(msg.data.length >= 28, "getTokenType: Not enough calldata");
return abi.encode(22, tokenType);
}
if (selector == this.deleteToken.selector) {
selfdestruct(msg.sender);
}
if (selector == this._update.selector) {
require(msg.data.length >= 124, "update: Not enough calldata");
address from = address(bytes20(msg.data[40:60]));
Expand Down
1 change: 0 additions & 1 deletion contracts/HtsSystemContractJson.sol
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ contract HtsSystemContractJson is HtsSystemContract {

assembly { slot := initialized.slot }
storeBool(address(this), uint256(slot), true);

_initTokenInfo(json);
}

Expand Down
Loading

0 comments on commit 53dd17f

Please sign in to comment.