Skip to content

Commit

Permalink
feat: returning non-success response code instead of reverting when s…
Browse files Browse the repository at this point in the history
…upplykey is missing (#167)

Signed-off-by: Mariusz Jasuwienas <[email protected]>
  • Loading branch information
arianejasuwienas committed Feb 7, 2025
1 parent 0850f4c commit aff16b6
Show file tree
Hide file tree
Showing 15 changed files with 270 additions and 23 deletions.
1 change: 1 addition & 0 deletions contracts/HederaResponseCodes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ pragma solidity ^0.8.0;

library HederaResponseCodes {
int32 internal constant SUCCESS = 22; // The transaction succeeded
int32 internal constant TOKEN_HAS_NO_SUPPLY_KEY = 180;
}
27 changes: 25 additions & 2 deletions contracts/HtsSystemContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,15 @@ contract HtsSystemContract is IHederaTokenService {
assembly { accountId := sload(slot) }
}

function mintToken(address token, int64 amount, bytes[] memory) htsCall external returns (
function mintToken(address token, int64 amount, bytes[] memory data) htsCall external returns (
int64 responseCode,
int64 newTotalSupply,
int64[] memory serialNumbers
) {
return mintToken(token, amount, false);
}

function mintToken(address token, int64 amount, bool ignoreSupplyKeyCheck) htsCall internal returns (
int64 responseCode,
int64 newTotalSupply,
int64[] memory serialNumbers
Expand All @@ -64,6 +72,9 @@ contract HtsSystemContract is IHederaTokenService {
require(amount > 0, "mintToken: invalid amount");

(int64 tokenInfoResponseCode, TokenInfo memory tokenInfo) = IHederaTokenService(token).getTokenInfo(token);
if (!ignoreSupplyKeyCheck && _extractKeyAddress(0x10, tokenInfo) == address(0)) { // 0x10 - supply key
return (HederaResponseCodes.TOKEN_HAS_NO_SUPPLY_KEY, tokenInfo.totalSupply, new int64[](0));
}
require(tokenInfoResponseCode == HederaResponseCodes.SUCCESS, "mintToken: failed to get token info");

address treasuryAccount = tokenInfo.token.treasury;
Expand All @@ -85,6 +96,9 @@ contract HtsSystemContract is IHederaTokenService {
require(amount > 0, "burnToken: invalid amount");

(int64 tokenInfoResponseCode, TokenInfo memory tokenInfo) = IHederaTokenService(token).getTokenInfo(token);
if (_extractKeyAddress(0x10, tokenInfo) == address(0)) {
return (HederaResponseCodes.TOKEN_HAS_NO_SUPPLY_KEY, tokenInfo.totalSupply);
}
require(tokenInfoResponseCode == HederaResponseCodes.SUCCESS, "burnToken: failed to get token info");

address treasuryAccount = tokenInfo.token.treasury;
Expand Down Expand Up @@ -188,7 +202,7 @@ contract HtsSystemContract is IHederaTokenService {
deployHIP719Proxy(tokenAddress);

if (initialTotalSupply > 0) {
this.mintToken(tokenAddress, initialTotalSupply, new bytes[](0));
mintToken(tokenAddress, initialTotalSupply, true);
}

responseCode = HederaResponseCodes.SUCCESS;
Expand Down Expand Up @@ -940,4 +954,13 @@ contract HtsSystemContract is IHederaTokenService {
assembly { sstore(slot, approved) }
emit IERC721.ApprovalForAll(sender, operator, approved);
}

function _extractKeyAddress(uint keyType, TokenInfo memory tokenInfo) private pure returns (address) {
for (uint256 i = 0; i < tokenInfo.token.tokenKeys.length; i++) {
if (tokenInfo.token.tokenKeys[i].keyType == keyType) {
return tokenInfo.token.tokenKeys[i].key.contractId;
}
}
return address(0);
}
}
8 changes: 5 additions & 3 deletions contracts/HtsSystemContractJson.sol
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ contract HtsSystemContractJson is HtsSystemContract {
return token;
}

function _getTokenKeys(string memory json) private pure returns (TokenKey[] memory tokenKeys) {
function _getTokenKeys(string memory json) private returns (TokenKey[] memory tokenKeys) {
tokenKeys = new TokenKey[](7);

try vm.parseJson(json, ".admin_key") returns (bytes memory keyBytes) {
Expand Down Expand Up @@ -320,16 +320,18 @@ contract HtsSystemContractJson is HtsSystemContract {
return tokenKeys;
}

function _getTokenKey(IMirrorNodeResponses.Key memory key, uint8 keyType) internal pure returns (TokenKey memory) {
function _getTokenKey(IMirrorNodeResponses.Key memory key, uint8 keyType) internal returns (TokenKey memory) {
bool inheritAccountKey = false;
address contractId = address(0);
address delegatableContractId = address(0);
bytes memory ed25519 = keccak256(bytes(key._type)) == keccak256(bytes("ED25519"))
? vm.parseBytes(key.key)
: new bytes(0);
bytes memory ECDSA_secp256k1 = keccak256(bytes(key._type)) == keccak256(bytes("ECDSA_SECP256K1"))
? vm.parseBytes(key.key)
: new bytes(0);
address contractId = ed25519.length + ECDSA_secp256k1.length > 0
? mirrorNode().getAccountAddressByPublicKey(key.key)
: address(0);
return TokenKey(
keyType,
KeyValue(
Expand Down
14 changes: 14 additions & 0 deletions contracts/MirrorNode.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ abstract contract MirrorNode {

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

function fetchAccountByPublicKey(string memory publicKey) external virtual returns (string memory json);

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

function fetchNonFungibleToken(address token, uint32 serial) external virtual returns (string memory json);
Expand Down Expand Up @@ -101,6 +103,18 @@ abstract contract MirrorNode {
return false;
}

function getAccountAddressByPublicKey(string memory publicKey) public returns (address) {
try this.fetchAccountByPublicKey(publicKey) returns (string memory json) {
if (vm.keyExistsJson(json, ".accounts[0].evm_address")) {
return vm.parseJsonAddress(json, ".accounts[0].evm_address");
}
} catch {
// Do nothing
}
// generate a deterministic address based on the account public key as a fallback
return address(uint160(uint256(keccak256(bytes(publicKey)))));
}

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

function fetchAccountByPublicKey(string memory publicKey) external override returns (string memory) {
return _get(string.concat("accounts?limit=1&account.publickey=", publicKey));
}

function fetchTokenRelationshipOfAccount(string memory idOrAliasOrEvmAddress, address token) external override returns (string memory) {
return _get(string.concat(
"accounts/",
Expand Down
10 changes: 10 additions & 0 deletions src/forwarder/mirror-node-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,16 @@ class MirrorNodeClient {
return this._get(`accounts/${idOrAliasOrEvmAddress}?transactions=false&${timestamp}`);
}

/**
* Fetches accounts information by account public key.
*
* @param {string} publicKey The account public key to fetch.
* @returns {Promise<{ accounts: {evm_address: string}[] } | null>} A `Promise` resolving to the account information or `null` if not found.
*/
async getAccountsByPublicKey(publicKey) {
return this._get(`accounts?limit=1&account.publickey=${publicKey}`);
}

/**
* @param {number} blockNumber
* @returns {Promise<{timestamp: {to: string}} | null>}
Expand Down
11 changes: 11 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,17 @@ interface IMirrorNodeClient {
account: string;
evm_address: string;
} | null>;

/**
* Get accounts by public key.
*
* This method should call the Mirror Node API endpoint `GET /api/v1/accounts?limit=1&account.publickey={publicKey}`.
*
* @param publicKey
*/
getAccountsByPublicKey(publicKey: string): Promise<{
accounts: { evm_address: string }[];
} | null>;
}

/**
Expand Down
15 changes: 15 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,21 @@ async function getHtsStorageAt(address, requestedSlot, blockNumber, mirrorNodeCl
if (unresolvedValues === undefined) {
const token = await mirrorNodeClient.getTokenById(tokenId, blockNumber);
if (token === null) return ret(ZERO_HEX_32_BYTE, `Token \`${tokenId}\` not found`);

for (const key of ['admin', 'kyc', 'freeze', 'wipe', 'supply', 'fee_schedule', 'pause']) {
const value = /**@type{{contractId: string}}*/ (token[`${key}_key`]);
if (!value) {
continue;
}
assert(typeof value === 'object' && 'key' in value && typeof value.key === 'string');
const result = await mirrorNodeClient.getAccountsByPublicKey(value.key);
if (result === undefined || result === null || result.accounts.length === 0) {
value.contractId = ZERO_HEX_32_BYTE;
continue;
}
value.contractId = result.accounts[0].evm_address;
}

unresolvedValues = slotMapOf(token).load(nrequestedSlot);

if (unresolvedValues === undefined)
Expand Down
16 changes: 13 additions & 3 deletions src/slotmap.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,13 +241,23 @@ function slotMapOf(token) {
['fee_schedule_key', 0x20],
['pause_key', 0x40],
]).map(([prop, key_type]) => {
const key = token[prop];
const key = /**@type{{contractId: string}}*/ (token[prop]);
if (key === null) return { key_type, ed25519: '', _e_c_d_s_a_secp256k1: '' };
assert(typeof key === 'object');
assert('_type' in key && 'key' in key);
if (key._type === 'ED25519')
return { key_type, ed25519: key.key, _e_c_d_s_a_secp256k1: '' };
return { key_type, ed25519: '', _e_c_d_s_a_secp256k1: key.key };
return {
key_type,
contract_id: key.contractId,
ed25519: key.key,
_e_c_d_s_a_secp256k1: '',
};
return {
key_type,
contract_id: key.contractId,
ed25519: '',
_e_c_d_s_a_secp256k1: key.key,
};
});
const customFees = /**@type {Record<string, Record<string, unknown>[]>}*/ (
token['custom_fees']
Expand Down
Loading

0 comments on commit aff16b6

Please sign in to comment.