Skip to content

Commit

Permalink
implement IHRC in HTS mocks
Browse files Browse the repository at this point in the history
Signed-off-by: Mo Shaikjee <[email protected]>
  • Loading branch information
mshakeg committed Jan 12, 2024
1 parent a3178b5 commit 350806c
Show file tree
Hide file tree
Showing 10 changed files with 446 additions and 26 deletions.
30 changes: 15 additions & 15 deletions contracts/exchange-rate-precompile/IExchangeRate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,24 @@
pragma solidity >=0.4.9 <0.9.0;

interface IExchangeRate {
// Given a value in tinycents (1e-8 US cents or 1e-10 USD), returns the
// equivalent value in tinybars (1e-8 HBAR) at the current exchange rate
// stored in system file 0.0.112.
//
// This rate is a weighted median of the the recent" HBAR-USD exchange
// rate on major exchanges, but should _not_ be treated as a live price
// oracle! It is important primarily because the network will use it to
// compute the tinybar fees for the active transaction.
//
// So a "self-funding" contract can use this rate to compute how much
// Given a value in tinycents (1e-8 US cents or 1e-10 USD), returns the
// equivalent value in tinybars (1e-8 HBAR) at the current exchange rate
// stored in system file 0.0.112.
//
// This rate is a weighted median of the the recent" HBAR-USD exchange
// rate on major exchanges, but should _not_ be treated as a live price
// oracle! It is important primarily because the network will use it to
// compute the tinybar fees for the active transaction.
//
// So a "self-funding" contract can use this rate to compute how much
// tinybar its users must send to cover the Hedera fees for the transaction.
function tinycentsToTinybars(uint256 tinycents) external returns (uint256);

// Given a value in tinybars (1e-8 HBAR), returns the equivalent value in
// tinycents (1e-8 US cents or 1e-10 USD) at the current exchange rate
// stored in system file 0.0.112.
//
// This rate tracks the the HBAR-USD rate on public exchanges, but
// Given a value in tinybars (1e-8 HBAR), returns the equivalent value in
// tinycents (1e-8 US cents or 1e-10 USD) at the current exchange rate
// stored in system file 0.0.112.
//
// This rate tracks the the HBAR-USD rate on public exchanges, but
// should _not_ be treated as a live price oracle! This conversion is
// less likely to be needed than the above conversion from tinycent to
// tinybars, but we include it for completeness.
Expand Down
10 changes: 9 additions & 1 deletion contracts/hts-precompile/IHRC.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.7;
pragma solidity >=0.4.9 <0.9.0;

interface IERCCommonToken {
function balanceOf(address account) external view returns (uint256);
}

interface IHRC {
function associate() external returns (uint256 responseCode);
function dissociate() external returns (uint256 responseCode);

function isAssociated(address evmAddress) external view returns (bool); // TODO: pending completion of https://hips.hedera.com/hip/hip-719 ??
}

interface IHRCCommon is IHRC, IERCCommonToken {}
16 changes: 8 additions & 8 deletions contracts/hts-precompile/IHederaTokenService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,7 @@ interface IHederaTokenService {
function getTokenDefaultFreezeStatus(address token)
external
returns (int64 responseCode, bool defaultFreezeStatus);

/// Query token default kyc status
/// @param token The token address to check
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
Expand Down Expand Up @@ -781,18 +781,18 @@ interface IHederaTokenService {

/// Query if valid token found for the given address
/// @param token The token address
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return isToken True if valid token found for the given address
function isToken(address token)
external returns
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return isToken True if valid token found for the given address
function isToken(address token)
external returns
(int64 responseCode, bool isToken);

/// Query to return the token type for a given address
/// @param token The token address
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return tokenType the token type. 0 is FUNGIBLE_COMMON, 1 is NON_FUNGIBLE_UNIQUE, -1 is UNRECOGNIZED
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return tokenType the token type. 0 is FUNGIBLE_COMMON, 1 is NON_FUNGIBLE_UNIQUE, -1 is UNRECOGNIZED
function getTokenType(address token)
external returns
external returns
(int64 responseCode, int32 tokenType);

/// Initiates a Redirect For Token
Expand Down
90 changes: 90 additions & 0 deletions test/foundry/HederaFungibleToken.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,48 @@ contract HederaFungibleTokenTest is HederaTokenUtils, HederaFungibleTokenUtils {
assertEq(success, true, "expected burn to succeed");
}

function test_CanAssociateAndDissociateDirectly() public {
IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](0);
address tokenAddress = _createSimpleMockFungibleToken(alice, keys);

bool success;
uint256 amount = 1e8;

TransferParams memory transferParams = TransferParams({
sender: alice,
token: tokenAddress,
from: alice,
to: bob,
amountOrSerialNumber: amount
});

TransferParams memory transferFromBobToAlice = TransferParams({
sender: bob,
token: tokenAddress,
from: bob,
to: alice,
amountOrSerialNumber: amount
});

(success, ) = _doTransferViaHtsPrecompile(transferParams);
assertEq(success, false, 'expected transfer to fail since recipient is not associated with token');

success = _doAssociateDirectly(bob, tokenAddress);
assertEq(success, true, 'expected bob to associate with token');

(success, ) = _doTransferViaHtsPrecompile(transferParams);
assertEq(success, true, 'expected transfer to succeed');

success = _doDissociateDirectly(bob, tokenAddress);
assertEq(success, false, 'expected bob to not dissociate with token while postive balance');

(success, ) = _doTransferViaHtsPrecompile(transferFromBobToAlice);
assertEq(success, true, 'expected transfer to succeed');

success = _doDissociateDirectly(bob, tokenAddress);
assertEq(success, true, 'expected bob to dissociate');
}

// negative cases
function test_CannotApproveIfSpenderNotAssociated() public {
/// @dev already demonstrated in some of the postive test cases
Expand All @@ -244,6 +286,54 @@ contract HederaFungibleTokenTest is HederaTokenUtils, HederaFungibleTokenUtils {
/// @dev already demonstrated in some of the postive test cases
// cannot transfer to recipient if recipient is not associated with HederaFungibleToken BOTH directly and viaHtsPrecompile
}

function test_CannotRepeatedlyAssociateAndDissociateDirectly() public {
IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](0);
address tokenAddress = _createSimpleMockFungibleToken(alice, keys);

bool success;
uint256 amount = 1e8;

TransferParams memory transferParams = TransferParams({
sender: alice,
token: tokenAddress,
from: alice,
to: bob,
amountOrSerialNumber: amount
});

TransferParams memory transferFromBobToAlice = TransferParams({
sender: bob,
token: tokenAddress,
from: bob,
to: alice,
amountOrSerialNumber: amount
});

(success, ) = _doTransferViaHtsPrecompile(transferParams);
assertEq(success, false, 'expected transfer to fail since recipient is not associated with token');

success = _doAssociateDirectly(bob, tokenAddress);
assertEq(success, true, 'expected bob to associate with token');

success = _doAssociateDirectly(bob, tokenAddress);
assertEq(success, false, 'expected bob to not re-associate with already associated token');

(success, ) = _doTransferViaHtsPrecompile(transferParams);
assertEq(success, true, 'expected transfer to succeed');

(success, ) = _doTransferViaHtsPrecompile(transferFromBobToAlice);
assertEq(success, true, 'expected transfer to succeed');

success = _doDissociateDirectly(bob, tokenAddress);
assertEq(success, true, 'expected bob to dissociate with token');

success = _doDissociateDirectly(bob, tokenAddress);
assertEq(success, false, 'expected bob to not re-dissociate with already unassociated token');

(success, ) = _doTransferViaHtsPrecompile(transferParams);
assertEq(success, false, 'expected transfer to fail since bob is not associated');
}
}

// forge test --match-contract HederaFungibleTokenTest --match-test test_CanBurnViaHtsPrecompile -vv
Expand Down
131 changes: 131 additions & 0 deletions test/foundry/HederaNonFungibleToken.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,68 @@ contract HederaNonFungibleTokenTest is HederaNonFungibleTokenUtils {
assertEq(success, true, "burn should succeed");
}

function test_CanAssociateAndDissociateDirectly() public {

bytes[] memory NULL_BYTES = new bytes[](1);

IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](1);
keys[0] = KeyHelper.getSingleKey(KeyHelper.KeyType.SUPPLY, KeyHelper.KeyValueType.CONTRACT_ID, alice);
address tokenAddress = _createSimpleMockNonFungibleToken(alice, keys);

bool success;
uint256 serialIdU256;

MintResponse memory mintResponse;
MintParams memory mintParams;
BurnParams memory burnParams;

mintParams = MintParams({
sender: alice,
token: tokenAddress,
mintAmount: 0
});

mintResponse = _doMintViaHtsPrecompile(mintParams);
serialIdU256 = uint64(mintResponse.serialId);

assertEq(mintResponse.success, true, "expected success since alice is supply key");

success = _doAssociateDirectly(bob, tokenAddress);
assertEq(success, true, 'expected bob to associate with token');

TransferParams memory transferParams;
TransferParams memory transferFromBobToAlice;

transferParams = TransferParams({
sender: alice,
token: tokenAddress,
from: alice,
to: bob,
amountOrSerialNumber: serialIdU256
});

transferFromBobToAlice = TransferParams({
sender: bob,
token: tokenAddress,
from: bob,
to: alice,
amountOrSerialNumber: serialIdU256
});

(success, ) = _doTransferDirectly(transferParams);
assertEq(success, true, 'expected success');

success = _doDissociateDirectly(bob, tokenAddress);
assertEq(success, false, 'expected bob to not dissociate with token while postive balance');

(success, ) = _doTransferDirectly(transferFromBobToAlice);
assertEq(success, true, 'expected transfer to succeed');

success = _doDissociateDirectly(bob, tokenAddress);
assertEq(success, true, 'expected bob to dissociate');

}

// negative cases
function test_CannotApproveIfSpenderNotAssociated() public {
/// @dev already demonstrated in some of the postive test cases
Expand All @@ -525,6 +587,75 @@ contract HederaNonFungibleTokenTest is HederaNonFungibleTokenUtils {
/// @dev already demonstrated in some of the postive test cases
// cannot transfer to recipient if recipient is not associated with HederaNonFungibleToken BOTH directly and viaHtsPrecompile
}

function test_CannotRepeatedlyAssociateAndDissociateDirectly() public {
bytes[] memory NULL_BYTES = new bytes[](1);

IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](1);
keys[0] = KeyHelper.getSingleKey(KeyHelper.KeyType.SUPPLY, KeyHelper.KeyValueType.CONTRACT_ID, alice);
address tokenAddress = _createSimpleMockNonFungibleToken(alice, keys);

bool success;
uint256 serialIdU256;

MintResponse memory mintResponse;
MintParams memory mintParams;
BurnParams memory burnParams;

mintParams = MintParams({
sender: alice,
token: tokenAddress,
mintAmount: 0
});

mintResponse = _doMintViaHtsPrecompile(mintParams);
serialIdU256 = uint64(mintResponse.serialId);

assertEq(mintResponse.success, true, "expected success since alice is supply key");

TransferParams memory transferParams;
TransferParams memory transferFromBobToAlice;

transferParams = TransferParams({
sender: alice,
token: tokenAddress,
from: alice,
to: bob,
amountOrSerialNumber: serialIdU256
});

transferFromBobToAlice = TransferParams({
sender: bob,
token: tokenAddress,
from: bob,
to: alice,
amountOrSerialNumber: serialIdU256
});

(success, ) = _doTransferDirectly(transferParams);
assertEq(success, false, 'expected transfer to fail since recipient is not associated with token');

success = _doAssociateDirectly(bob, tokenAddress);
assertEq(success, true, 'expected bob to associate with token');

success = _doAssociateDirectly(bob, tokenAddress);
assertEq(success, false, 'expected bob to not re-associate with already associated token');

(success, ) = _doTransferDirectly(transferParams);
assertEq(success, true, 'expected transfer to succeed');

(success, ) = _doTransferDirectly(transferFromBobToAlice);
assertEq(success, true, 'expected transfer to succeed');

success = _doDissociateDirectly(bob, tokenAddress);
assertEq(success, true, 'expected bob to dissociate with token');

success = _doDissociateDirectly(bob, tokenAddress);
assertEq(success, false, 'expected bob to not re-dissociate with already unassociated token');

(success, ) = _doTransferDirectly(transferParams);
assertEq(success, false, 'expected transfer to fail since bob is not associated');
}
}

// forge test --match-contract HederaNonFungibleTokenTest --match-test test_TransferUsingAllowanceDirectly -vv
Expand Down
19 changes: 18 additions & 1 deletion test/foundry/mocks/hts-precompile/HederaFungibleToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import '@openzeppelin/contracts/token/ERC20/ERC20.sol';

import '../../../../contracts/hts-precompile/HederaResponseCodes.sol';
import '../../../../contracts/hts-precompile/IHederaTokenService.sol';
import '../../../../contracts/hts-precompile/IHRC.sol';
import './HtsSystemContractMock.sol';
import '../../../../contracts/libraries/Constants.sol';

contract HederaFungibleToken is ERC20, Constants {
contract HederaFungibleToken is IHRC, ERC20, Constants {
error HtsPrecompileError(int64 responseCode);
HtsSystemContractMock internal constant HtsPrecompile = HtsSystemContractMock(HTS_PRECOMPILE);

Expand Down Expand Up @@ -103,4 +104,20 @@ contract HederaFungibleToken is ERC20, Constants {
function decimals() public view override returns (uint8) {
return _decimals;
}

// IHRC setters:

function associate() external returns (uint256 responseCode) {
responseCode = uint64(HtsPrecompile.preAssociate(msg.sender));
}

function dissociate() external returns (uint256 responseCode) {
responseCode = uint64(HtsPrecompile.preDissociate(msg.sender));
}

// IHRC getters:

function isAssociated(address evmAddress) external view override returns (bool) {
return HtsPrecompile.isAssociated(evmAddress, address(this));
}
}
Loading

0 comments on commit 350806c

Please sign in to comment.