Skip to content

Commit

Permalink
feat: [Foundry] Fetch HIP-719 remote state for fungible tokens (#143)
Browse files Browse the repository at this point in the history
Signed-off-by: Victor Yanev <[email protected]>
  • Loading branch information
victor-yanev authored Dec 3, 2024
1 parent e178113 commit 71e0f30
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 0 deletions.
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.JSON_RPC == testMode) {
// 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);
}
}

0 comments on commit 71e0f30

Please sign in to comment.