Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [Foundry] Fetch HIP-719 remote state for fungible tokens #143

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"tokens": [
{
"automatic_association": false,
"balance": 5000,
"created_timestamp": "1724382479.562855337",
"decimals": 0,
"token_id": "0.0.4730999",
"freeze_status": "NOT_APPLICABLE",
"kyc_status": "NOT_APPLICABLE"
}
],
"links": {
"next": null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"tokens": [
{
"automatic_association": false,
"balance": 5000000,
"created_timestamp": "1706825561.156945003",
"decimals": 6,
"token_id": "0.0.429274",
"freeze_status": "UNFROZEN",
"kyc_status": "NOT_APPLICABLE"
}
],
"links": {
"next": null
}
}
15 changes: 15 additions & 0 deletions scripts/curl
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,21 @@ for (const [re, fn] of /** @type {const} */([
return undefined;
}
}],
[/^accounts\/((0x[0-9a-fA-F]{40})|(0\.0\.\d+))\/tokens/, idOrAliasOrEvmAddress => {
assert(typeof idOrAliasOrEvmAddress === 'string');
const tokenId = searchParams.get('token.id');
assert(tokenId !== null);

const token = tokens[tokenId];
if (token === undefined)
return { tokens: [], links: { next: null } };

try {
return require(`../@hts-forking/test/data/${token.symbol}/getTokenRelationship_${idOrAliasOrEvmAddress.toLowerCase()}.json`);
} catch {
return undefined;
}
}],
])) {
const match = endpoint.match(re);
if (match !== null) {
Expand Down
9 changes: 9 additions & 0 deletions src/HtsSystemContractJson.sol
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,15 @@ contract HtsSystemContractJson is HtsSystemContract {
return slot;
}

function _isAssociatedSlot(address account) internal override returns (bytes32) {
bytes32 slot = super._isAssociatedSlot(account);
if (vm.load(_scratchAddr(), slot) == bytes32(0)) {
bool associated = mirrorNode().isAssociated(address(this), account);
_setValue(slot, bytes32(uint256(associated ? 1 : 0)));
}
return slot;
}

function _setValue(bytes32 slot, bytes32 value) private {
vm.store(address(this), slot, value);
vm.store(_scratchAddr(), slot, bytes32(uint(1)));
Expand Down
10 changes: 10 additions & 0 deletions src/IMirrorNodeResponses.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,14 @@ interface IMirrorNodeResponses {
int64 amount;
string denominating_token_id;
}

struct TokenRelationship {
bool automatic_association;
uint256 balance;
string created_timestamp;
uint256 decimals;
string token_id;
string freeze_status;
string kyc_status;
}
}
14 changes: 14 additions & 0 deletions src/MirrorNode.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "./IMirrorNodeResponses.sol";
import {Vm} from "forge-std/Vm.sol";

abstract contract MirrorNode {
Expand All @@ -25,6 +26,8 @@ abstract contract MirrorNode {

function fetchAccount(string memory account) external virtual returns (string memory json);

function fetchTokenRelationshipOfAccount(string memory account, address token) external virtual returns (string memory json);

function getBalance(address token, address account) external returns (uint256) {
uint32 accountNum = _getAccountNum(account);
if (accountNum == 0) return 0;
Expand All @@ -51,6 +54,17 @@ abstract contract MirrorNode {
return 0;
}

function isAssociated(address token, address account) external returns (bool) {
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[]));
return relationships.length > 0;
}
} catch {}
return false;
}

function getAccountAddress(string memory accountId) external returns (address) {
if (bytes(accountId).length == 0
|| keccak256(bytes(accountId)) == keccak256(bytes("null"))
Expand Down
9 changes: 9 additions & 0 deletions src/MirrorNodeFFI.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ contract MirrorNodeFFI is MirrorNode {
));
}

function fetchTokenRelationshipOfAccount(string memory idOrAliasOrEvmAddress, address token) external override returns (string memory) {
return _get(string.concat(
"accounts/",
idOrAliasOrEvmAddress,
"/tokens?token.id=0.0.",
vm.toString(uint160(token))
));
}

/**
* @dev Returns the block information by given number.
*
Expand Down
19 changes: 19 additions & 0 deletions test/IHRC719.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,23 @@ contract IHRC719TokenAssociationTest is Test, TestSetup {

vm.stopPrank();
}

function test_IHRC719_with_real_accounts() external {
if (testMode() == TestMode.JSON_RPC) {
// TODO: Enable this test with https://github.com/hashgraph/hedera-forking/issues/126
return;
}
vm.startPrank(USDC_TREASURY);
assertEq(IHRC719(USDC).isAssociated(), true);
assertEq(IHRC719(USDC).dissociate(), 1);
assertEq(IHRC719(USDC).isAssociated(), false);


vm.startPrank(MFCT_TREASURY);
assertEq(IHRC719(MFCT).isAssociated(), true);
assertEq(IHRC719(MFCT).dissociate(), 1);
assertEq(IHRC719(MFCT).isAssociated(), false);

vm.stopPrank();
}
}
6 changes: 6 additions & 0 deletions test/lib/MirrorNodeMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,10 @@ contract MirrorNodeMock is MirrorNode {
string memory path = string.concat("./@hts-forking/test/data/getAccount_", vm.toLowercase(account), ".json");
return vm.readFile(path);
}

function fetchTokenRelationshipOfAccount(string memory idOrAliasOrEvmAddress, address token) external override view returns (string memory) {
string memory symbol = _symbolOf[token];
string memory path = string.concat("./@hts-forking/test/data/", symbol, "/getTokenRelationship_", vm.toLowercase(idOrAliasOrEvmAddress), ".json");
return vm.readFile(path);
}
}
Loading