Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions contracts/HederaResponseCodes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,13 @@
pragma solidity ^0.8.0;

library HederaResponseCodes {
int32 internal constant NOT_SUPPORTED = 13;
int32 internal constant SUCCESS = 22; // The transaction succeeded
int32 internal constant INSUFFICIENT_ACCOUNT_BALANCE = 28;
int32 internal constant INVALID_ACCOUNT_AMOUNTS = 48;
int32 internal constant ACCOUNT_REPEATED_IN_ACCOUNT_AMOUNTS = 74;
int32 internal constant INSUFFICIENT_TOKEN_BALANCE = 178;
int32 internal constant SENDER_DOES_NOT_OWN_NFT_SERIAL_NO = 237;
int32 internal constant SPENDER_DOES_NOT_HAVE_ALLOWANCE = 292;
int32 internal constant MAX_ALLOWANCES_EXCEEDED = 294;
}
332 changes: 312 additions & 20 deletions contracts/HtsSystemContract.sol

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions contracts/HtsSystemContractJson.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity ^0.8.0;

import {Vm} from "forge-std/Vm.sol";
import {decode} from './Base64.sol';
import {HederaResponseCodes} from "./HederaResponseCodes.sol";
import {HtsSystemContract, HTS_ADDRESS} from "./HtsSystemContract.sol";
import {IERC20} from "./IERC20.sol";
import {MirrorNode} from "./MirrorNode.sol";
Expand Down Expand Up @@ -435,6 +436,15 @@ contract HtsSystemContractJson is HtsSystemContract {
return slot;
}

function _hasKycGrantedSlot(address account) internal override returns (bytes32) {
bytes32 slot = super._hasKycGrantedSlot(account);
if (_shouldFetch(slot)) {
string memory kycStatus = mirrorNode().getKycStatus(address(this), account);
_setValue(slot, bytes32(keccak256(bytes(kycStatus)) == keccak256("GRANTED") ? uint256(1) : uint256(0)));
}
return slot;
}

function _allowanceSlot(address owner, address spender) internal override returns (bytes32) {
bytes32 slot = super._allowanceSlot(owner, spender);
if (_shouldFetch(slot)) {
Expand Down Expand Up @@ -503,4 +513,9 @@ contract HtsSystemContractJson is HtsSystemContract {
function _scratchAddr() private view returns (address) {
return address(bytes20(keccak256(abi.encode(address(this)))));
}

function _updateHbarBalanceOnAccount(address account, uint256 newBalance) internal override returns (int64) {
vm.deal(account, newBalance);
return HederaResponseCodes.SUCCESS;
}
}
24 changes: 12 additions & 12 deletions contracts/IHederaTokenService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -304,9 +304,9 @@ interface IHederaTokenService {
/// @param transferList the list of hbar transfers to do
/// @param tokenTransfers the list of token transfers to do
/// @custom:version 0.3.0 the signature of the previous version was cryptoTransfer(TokenTransferList[] memory tokenTransfers)
// function cryptoTransfer(TransferList memory transferList, TokenTransferList[] memory tokenTransfers)
// external
// returns (int64 responseCode);
function cryptoTransfer(TransferList memory transferList, TokenTransferList[] memory tokenTransfers)
external
returns (int64 responseCode);

/// Mints an amount of the token to the defined treasury account
/// @param token The token for which to mint tokens. If token does not exist, transaction results in
Expand Down 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
13 changes: 13 additions & 0 deletions contracts/MirrorNode.sol
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,19 @@ abstract contract MirrorNode {
return false;
}

function getKycStatus(address token, address account) external returns (string memory) {
try this.fetchTokenRelationshipOfAccount(vm.toString(account), token) returns (string memory json) {
if (vm.keyExistsJson(json, ".tokens")) {
bytes memory tokens = vm.parseJson(json, ".tokens");
IMirrorNodeResponses.TokenRelationship[] memory relationships = abi.decode(tokens, (IMirrorNodeResponses.TokenRelationship[]));
if (relationships.length > 0) {
return relationships[0].kyc_status;
}
}
} catch {}
return "NOT_APPLICABLE";
}

function getAccountAddress(string memory accountId) public returns (address) {
if (bytes(accountId).length == 0
|| keccak256(bytes(accountId)) == keccak256(bytes("null"))
Expand Down
2 changes: 2 additions & 0 deletions examples/hardhat-hts-crypto-transfer-hbar/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# If you want to deploy on testnet go to https://portal.hedera.com/ to setup your private key
TESTNET_OPERATOR_PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000000
11 changes: 11 additions & 0 deletions examples/hardhat-hts-crypto-transfer-hbar/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
node_modules
.env
coverage
coverage.json
typechain
typechain-types
types

#Hardhat files
cache
artifacts
21 changes: 21 additions & 0 deletions examples/hardhat-hts-crypto-transfer-hbar/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# HTS crypto transfer hbars example

Simple scripts to show and investigate how the crypto transfer actually works for the hbar operations on the testnet.

Requires testnet account with non-empty balance. Each test run will result in decreasing the balance.

## Configuration

Create `.env` file based on `.env.example`

```
# Alias accounts keys
TESTNET_OPERATOR_PRIVATE_KEY=
```

## Setup & Install

In the project directory:

1. Run `npm install`
2. Run `npx hardhat test`
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
// pragma experimental ABIEncoderV2;

interface IHederaTokenService {

// /// Transfers cryptocurrency among two or more accounts by making the desired adjustments to their
// /// balances. Each transfer list can specify up to 10 adjustments. Each negative amount is withdrawn
// /// from the corresponding account (a sender), and each positive one is added to the corresponding
// /// account (a receiver). The amounts list must sum to zero. Each amount is a number of tinybars
// /// (there are 100,000,000 tinybars in one hbar). If any sender account fails to have sufficient
// /// hbars, then the entire transaction fails, and none of those transfers occur, though the
// /// transaction fee is still charged. This transaction must be signed by the keys for all the sending
// /// accounts, and for any receiving accounts that have receiverSigRequired == true. The signatures
// /// are in the same order as the accounts, skipping those accounts that don't need a signature.
// /// @custom:version 0.3.0 previous version did not include isApproval
struct AccountAmount {
// The Account ID, as a solidity address, that sends/receives cryptocurrency or tokens
address accountID;

// The amount of the lowest denomination of the given token that
// the account sends(negative) or receives(positive)
int64 amount;

// If true then the transfer is expected to be an approved allowance and the
// accountID is expected to be the owner. The default is false (omitted).
bool isApproval;
}

// /// A sender account, a receiver account, and the serial number of an NFT of a Token with
// /// NON_FUNGIBLE_UNIQUE type. When minting NFTs the sender will be the default AccountID instance
// /// (0.0.0 aka 0x0) and when burning NFTs, the receiver will be the default AccountID instance.
// /// @custom:version 0.3.0 previous version did not include isApproval
struct NftTransfer {
// The solidity address of the sender
address senderAccountID;

// The solidity address of the receiver
address receiverAccountID;

// The serial number of the NFT
int64 serialNumber;

// If true then the transfer is expected to be an approved allowance and the
// accountID is expected to be the owner. The default is false (omitted).
bool isApproval;
}

struct TokenTransferList {
// The ID of the token as a solidity address
address token;

// Applicable to tokens of type FUNGIBLE_COMMON. Multiple list of AccountAmounts, each of which
// has an account and amount.
AccountAmount[] transfers;

// Applicable to tokens of type NON_FUNGIBLE_UNIQUE. Multiple list of NftTransfers, each of
// which has a sender and receiver account, including the serial number of the NFT
NftTransfer[] nftTransfers;
}

struct TransferList {
// Multiple list of AccountAmounts, each of which has an account and amount.
// Used to transfer hbars between the accounts in the list.
AccountAmount[] transfers;
}

/// Performs transfers among combinations of tokens and hbars
/// @param transferList the list of hbar transfers to do
/// @param tokenTransfers the list of token transfers to do
/// @custom:version 0.3.0 the signature of the previous version was cryptoTransfer(TokenTransferList[] memory tokenTransfers)
function cryptoTransfer(TransferList memory transferList, TokenTransferList[] memory tokenTransfers)
external
returns (int64 responseCode);
}
50 changes: 50 additions & 0 deletions examples/hardhat-hts-crypto-transfer-hbar/hardhat.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*-
* Hedera Hardhat Forking Plugin
*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

require('dotenv').config();
require('@nomicfoundation/hardhat-toolbox');
require('@nomicfoundation/hardhat-chai-matchers');

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
mocha: {
timeout: 3600000,
},
solidity: {
version: '0.8.9',
settings: {
optimizer: {
enabled: true,
runs: 500,
},
},
},
defaultNetwork: 'testnet',
networks: {
hardhat: {
allowUnlimitedContractSize: true,
},
testnet: {
url: 'https://testnet.hashio.io/api',
accounts: process.env.TESTNET_OPERATOR_PRIVATE_KEY
? [process.env.TESTNET_OPERATOR_PRIVATE_KEY]
: [],
chainId: 296,
},
},
};
Loading