Skip to content

Commit b98c08e

Browse files
authored
feat: add HTS foundry scripts (#291)
Signed-off-by: Luis Mastrangelo <luis@swirldslabs.com>
1 parent 14478fc commit b98c08e

File tree

6 files changed

+100
-55
lines changed

6 files changed

+100
-55
lines changed

README.md

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -195,52 +195,39 @@ For example
195195
// SPDX-License-Identifier: Apache-2.0
196196
pragma solidity ^0.8.0;
197197
198-
import {Script, console} from "forge-std/Script.sol";
198+
import {Script} from "forge-std/Script.sol";
199199
import {HTS_ADDRESS} from "hedera-forking/HtsSystemContract.sol";
200200
import {IHederaTokenService} from "hedera-forking/IHederaTokenService.sol";
201-
import {HederaResponseCodes} from "hedera-forking/HederaResponseCodes.sol";
202201
import {Hsc} from "hedera-forking/Hsc.sol";
203202
204203
/**
205204
* Given how Foundry script works, the flag `--skip-simulation` is necessary.
206205
* For example
207206
*
208-
* forge script scripts/CreateToken.s.sol -vvv --rpc-url testnet --skip-simulation --broadcast
207+
* forge script CreateTokenScript --rpc-url testnet --broadcast --skip-simulation
209208
*/
210209
contract CreateTokenScript is Script {
211-
uint256 PRIVATE_KEY = vm.envUint("PRIVATE_KEY");
212-
213-
function run() external returns (int64 responseCode, address tokenAddress) {
210+
function run() external returns (address signer, int64 responseCode, address tokenAddress) {
214211
Hsc.htsSetup();
215212
216-
address signer = vm.addr(PRIVATE_KEY);
217-
console.log("Signer address %s", signer);
218-
219-
vm.startBroadcast(PRIVATE_KEY);
213+
uint256 PRIVATE_KEY = vm.envUint("PRIVATE_KEY");
214+
signer = vm.addr(PRIVATE_KEY);
220215
221216
IHederaTokenService.KeyValue memory keyValue;
222217
keyValue.inheritAccountKey = true;
223218
224-
IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](1);
225-
keys[0] = IHederaTokenService.TokenKey(1, keyValue);
226-
227-
IHederaTokenService.Expiry memory expiry;
228-
expiry.second = 0;
229-
expiry.autoRenewAccount = signer;
230-
expiry.autoRenewPeriod = 8000000;
231-
232219
IHederaTokenService.HederaToken memory token;
233220
token.name = "HTS Token Example Created with Foundry";
234221
token.symbol = "FDRY";
235222
token.treasury = signer;
236223
token.memo = "This HTS Token was created using `forge script` together with HTS emulation";
237-
token.tokenKeys = keys;
238-
token.expiry = expiry;
224+
token.tokenKeys = new IHederaTokenService.TokenKey[](2);
225+
token.tokenKeys[0] = IHederaTokenService.TokenKey(0x1, keyValue); // Admin Key
226+
token.tokenKeys[1] = IHederaTokenService.TokenKey(0x10, keyValue); // Supply Key enables minting
227+
token.expiry = IHederaTokenService.Expiry(0, signer, 8000000);
239228
229+
vm.startBroadcast(PRIVATE_KEY);
240230
(responseCode, tokenAddress) = IHederaTokenService(HTS_ADDRESS).createFungibleToken{value: 10 ether}(token, 10000, 4);
241-
console.log("Response code %d", int(responseCode));
242-
console.log("Token address %s", tokenAddress);
243-
244231
vm.stopBroadcast();
245232
}
246233
}

contracts/HtsSystemContract.sol

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -584,14 +584,14 @@ contract HtsSystemContract is IHederaTokenService {
584584
if (selector == this.associateToken.selector) {
585585
require(msg.data.length >= 48, "associateToken: Not enough calldata");
586586
address account = address(bytes20(msg.data[40:60]));
587-
bytes32 slot = _isAssociatedSlot(account);
587+
bytes32 slot = _isAssociatedSlot(account, false);
588588
assembly { sstore(slot, true) }
589589
return abi.encode(HederaResponseCodes.SUCCESS);
590590
}
591591
if (selector == this.dissociateToken.selector) {
592592
require(msg.data.length >= 48, "dissociateToken: Not enough calldata");
593593
address account = address(bytes20(msg.data[40:60]));
594-
bytes32 slot = _isAssociatedSlot(account);
594+
bytes32 slot = _isAssociatedSlot(account, false);
595595
assembly { sstore(slot, false) }
596596
return abi.encode(HederaResponseCodes.SUCCESS);
597597
}
@@ -797,17 +797,17 @@ contract HtsSystemContract is IHederaTokenService {
797797

798798
function _redirectForHRC719(bytes4 selector) private returns (bytes memory) {
799799
if (selector == IHRC719.associate.selector) {
800-
bytes32 slot = _isAssociatedSlot(msg.sender);
800+
bytes32 slot = _isAssociatedSlot(msg.sender, false);
801801
assembly { sstore(slot, true) }
802802
return abi.encode(true);
803803
}
804804
if (selector == IHRC719.dissociate.selector) {
805-
bytes32 slot = _isAssociatedSlot(msg.sender);
805+
bytes32 slot = _isAssociatedSlot(msg.sender, false);
806806
assembly { sstore(slot, false) }
807807
return abi.encode(true);
808808
}
809809
if (selector == IHRC719.isAssociated.selector) {
810-
bytes32 slot = _isAssociatedSlot(msg.sender);
810+
bytes32 slot = _isAssociatedSlot(msg.sender, true);
811811
bool res;
812812
assembly { res := sload(slot) }
813813
return abi.encode(res);
@@ -892,11 +892,11 @@ contract HtsSystemContract is IHederaTokenService {
892892
return bytes32(abi.encodePacked(selector, pad, spenderId, ownerId));
893893
}
894894

895-
function _isAssociatedSlot(address account) internal virtual returns (bytes32) {
895+
function _isAssociatedSlot(address account, bool revertIfNotExists) internal virtual returns (bytes32) {
896896
bytes4 selector = IHRC719.isAssociated.selector;
897897
uint192 pad = 0x0;
898898
(uint32 accountId, bool exists) = HtsSystemContract(HTS_ADDRESS).getAccountId(account);
899-
require(exists);
899+
require(!revertIfNotExists || exists, "_isAssociatedSlot: account does not exist");
900900
return bytes32(abi.encodePacked(selector, pad, accountId));
901901
}
902902

contracts/HtsSystemContractJson.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -487,8 +487,8 @@ contract HtsSystemContractJson is HtsSystemContract {
487487
return slot;
488488
}
489489

490-
function _isAssociatedSlot(address account) internal override returns (bytes32) {
491-
bytes32 slot = super._isAssociatedSlot(account);
490+
function _isAssociatedSlot(address account, bool revertIfNotExists) internal override returns (bytes32) {
491+
bytes32 slot = super._isAssociatedSlot(account, revertIfNotExists);
492492
if (_shouldFetch(slot)) {
493493
bool associated = mirrorNode().isAssociated(address(this), account);
494494
_setValue(slot, bytes32(uint256(associated ? 1 : 0)));
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
import {Script, VmSafe} from "forge-std/Script.sol";
5+
import {IERC20} from "hedera-forking/IERC20.sol";
6+
import {IHRC719} from "hedera-forking/IHRC719.sol";
7+
import {Hsc} from "hedera-forking/Hsc.sol";
8+
9+
contract Approver {
10+
function associate(address tokenAddress) external {
11+
IHRC719(tokenAddress).associate();
12+
}
13+
14+
function approve(address tokenAddress, address account, uint256 amount) external {
15+
IERC20(tokenAddress).approve(account, amount);
16+
}
17+
}
18+
19+
/**
20+
* Usage
21+
*
22+
* forge script ApproveTokenScript --sig "run(address)" $TOKEN_ADDR --rpc-url testnet --broadcast --skip-simulation --slow
23+
*/
24+
contract ApproveTokenScript is Script {
25+
function run(address tokenAddress) external returns (Approver approver, address payable account) {
26+
Hsc.htsSetup();
27+
28+
uint256 PRIVATE_KEY = vm.envUint("PRIVATE_KEY");
29+
30+
vm.startBroadcast(PRIVATE_KEY);
31+
approver = new Approver();
32+
vm.stopBroadcast();
33+
34+
vm.startBroadcast(PRIVATE_KEY);
35+
Approver(approver).associate(tokenAddress);
36+
37+
IERC20(tokenAddress).transfer(address(approver), 50000);
38+
39+
VmSafe.Wallet memory wallet = vm.createWallet("wallet");
40+
account = payable(wallet.addr);
41+
account.transfer(1 ether); // 1 HBAR
42+
43+
Approver(approver).approve(tokenAddress, account, 400_0000);
44+
45+
vm.stopBroadcast();
46+
}
47+
}
Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,39 @@
11
// SPDX-License-Identifier: Apache-2.0
22
pragma solidity ^0.8.0;
33

4-
import {Script, console} from "forge-std/Script.sol";
4+
import {Script} from "forge-std/Script.sol";
55
import {HTS_ADDRESS} from "hedera-forking/HtsSystemContract.sol";
66
import {IHederaTokenService} from "hedera-forking/IHederaTokenService.sol";
7-
import {HederaResponseCodes} from "hedera-forking/HederaResponseCodes.sol";
87
import {Hsc} from "hedera-forking/Hsc.sol";
98

109
/**
1110
* Given how Foundry script works, the flag `--skip-simulation` is necessary.
1211
* For example
1312
*
14-
* forge script scripts/CreateToken.s.sol -vvv --rpc-url testnet --skip-simulation --broadcast
13+
* forge script CreateTokenScript --rpc-url testnet --broadcast --skip-simulation
1514
*/
1615
contract CreateTokenScript is Script {
17-
uint256 PRIVATE_KEY = vm.envUint("PRIVATE_KEY");
18-
19-
function run() external returns (int64 responseCode, address tokenAddress) {
16+
function run() external returns (address signer, int64 responseCode, address tokenAddress) {
2017
Hsc.htsSetup();
2118

22-
address signer = vm.addr(PRIVATE_KEY);
23-
console.log("Signer address %s", signer);
24-
25-
vm.startBroadcast(PRIVATE_KEY);
19+
uint256 PRIVATE_KEY = vm.envUint("PRIVATE_KEY");
20+
signer = vm.addr(PRIVATE_KEY);
2621

2722
IHederaTokenService.KeyValue memory keyValue;
2823
keyValue.inheritAccountKey = true;
2924

30-
IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](1);
31-
keys[0] = IHederaTokenService.TokenKey(1, keyValue);
32-
33-
IHederaTokenService.Expiry memory expiry;
34-
expiry.second = 0;
35-
expiry.autoRenewAccount = signer;
36-
expiry.autoRenewPeriod = 8000000;
37-
3825
IHederaTokenService.HederaToken memory token;
3926
token.name = "HTS Token Example Created with Foundry";
4027
token.symbol = "FDRY";
4128
token.treasury = signer;
4229
token.memo = "This HTS Token was created using `forge script` together with HTS emulation";
43-
token.tokenKeys = keys;
44-
token.expiry = expiry;
30+
token.tokenKeys = new IHederaTokenService.TokenKey[](2);
31+
token.tokenKeys[0] = IHederaTokenService.TokenKey(0x1, keyValue); // Admin Key
32+
token.tokenKeys[1] = IHederaTokenService.TokenKey(0x10, keyValue); // Supply Key enables minting
33+
token.expiry = IHederaTokenService.Expiry(0, signer, 8000000);
4534

35+
vm.startBroadcast(PRIVATE_KEY);
4636
(responseCode, tokenAddress) = IHederaTokenService(HTS_ADDRESS).createFungibleToken{value: 10 ether}(token, 10000, 4);
47-
console.log("Response code %d", int(responseCode));
48-
console.log("Token address %s", tokenAddress);
49-
5037
vm.stopBroadcast();
5138
}
5239
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
import {Script} from "forge-std/Script.sol";
5+
import {HTS_ADDRESS} from "hedera-forking/HtsSystemContract.sol";
6+
import {IHederaTokenService} from "hedera-forking/IHederaTokenService.sol";
7+
import {Hsc} from "hedera-forking/Hsc.sol";
8+
9+
/**
10+
* Replace the variables `$TOKEN_ADDR` and `$AMOUNT` with the desired values.
11+
*
12+
* forge script MintTokenScript --sig "run(address,uint)" $TOKEN_ADDR $AMOUNT --rpc-url testnet --broadcast --skip-simulation
13+
*/
14+
contract MintTokenScript is Script {
15+
function run(address tokenAddress, uint256 amount) external returns (int64 responseCode, int64 newTotalSupply) {
16+
Hsc.htsSetup();
17+
18+
uint256 PRIVATE_KEY = vm.envUint("PRIVATE_KEY");
19+
20+
vm.startBroadcast(PRIVATE_KEY);
21+
(responseCode, newTotalSupply,) = IHederaTokenService(HTS_ADDRESS).mintToken(tokenAddress, int64(int256(amount)), new bytes[](0));
22+
vm.stopBroadcast();
23+
}
24+
}

0 commit comments

Comments
 (0)