@mapprotocol/common-contracts 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -100
- package/package.json +14 -1
- package/script/base/Base.s.sol +231 -0
- package/script/base/interfaces/IFactory.sol +7 -0
- package/utils/deployer.ts +15 -3
- package/utils/dist/deployer.d.ts +12 -0
- package/utils/dist/verifier.d.ts +10 -0
- package/utils/dist/verifier.js +57 -33
- package/utils/verifier.ts +72 -41
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ npm install @mapprotocol/common-contracts
|
|
|
12
12
|
|
|
13
13
|
### BaseImplementation
|
|
14
14
|
|
|
15
|
-
Abstract base for all upgradeable protocol contracts
|
|
15
|
+
Abstract base for all upgradeable protocol contracts (UUPS + Pausable + AccessManaged).
|
|
16
16
|
|
|
17
17
|
```solidity
|
|
18
18
|
import "@mapprotocol/common-contracts/contracts/base/BaseImplementation.sol";
|
|
@@ -24,12 +24,6 @@ contract MyContract is BaseImplementation {
|
|
|
24
24
|
}
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
**Includes:**
|
|
28
|
-
- `UUPSUpgradeable` — proxy upgrade pattern
|
|
29
|
-
- `PausableUpgradeable` — emergency pause via `trigger()`
|
|
30
|
-
- `AccessManagedUpgradeable` — role-based function access
|
|
31
|
-
- `getImplementation()` — read current implementation address
|
|
32
|
-
|
|
33
27
|
### AuthorityManager
|
|
34
28
|
|
|
35
29
|
Extended `AccessManager` with enumerable role members. One instance per chain controls all protocol contracts.
|
|
@@ -38,82 +32,49 @@ Extended `AccessManager` with enumerable role members. One instance per chain co
|
|
|
38
32
|
import "@mapprotocol/common-contracts/contracts/AuthorityManager.sol";
|
|
39
33
|
```
|
|
40
34
|
|
|
41
|
-
**Key functions:**
|
|
42
|
-
- `grantRole(roleId, account, delay)` — assign role
|
|
43
|
-
- `revokeRole(roleId, account)` — remove role
|
|
44
|
-
- `setTargetFunctionRole(target, selectors, roleId)` — restrict contract functions to a role
|
|
45
|
-
- `getRoleMembers(roleId)` — list all members of a role
|
|
46
|
-
|
|
47
35
|
## TypeScript Utilities
|
|
48
36
|
|
|
49
|
-
### Deployment
|
|
50
|
-
|
|
51
37
|
```typescript
|
|
52
|
-
import {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
// With custom suffix and path
|
|
58
|
-
const addr = await getDeploymentByKey("Bsc", "Gateway", { suffix: "main", basePath: "./my-deployments" });
|
|
59
|
-
|
|
60
|
-
// Save deployment
|
|
61
|
-
await saveDeployment("Bsc", "Gateway", "0x...");
|
|
38
|
+
import { createDeployer } from "@mapprotocol/common-contracts/utils/deployer";
|
|
39
|
+
import { getDeploymentByKey, saveDeployment } from "@mapprotocol/common-contracts/utils/deployRecord";
|
|
40
|
+
import { TronClient, tronToHex, tronFromHex } from "@mapprotocol/common-contracts/utils/tronHelper";
|
|
41
|
+
import { verify } from "@mapprotocol/common-contracts/utils/verifier";
|
|
42
|
+
import { addressToHex } from "@mapprotocol/common-contracts/utils/addressCodec";
|
|
62
43
|
```
|
|
63
44
|
|
|
64
|
-
###
|
|
45
|
+
### Quick Start
|
|
65
46
|
|
|
66
47
|
```typescript
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
48
|
+
// Unified deployer — auto-routes EVM / Tron
|
|
49
|
+
const deployer = createDeployer(hre, { autoVerify: true });
|
|
50
|
+
let result = await deployer.deploy("Gateway", [bridge, owner, wtoken]);
|
|
51
|
+
let proxy = await deployer.deployProxy("Gateway", [admin]);
|
|
52
|
+
|
|
53
|
+
// Deploy record
|
|
54
|
+
const addr = getDeploymentByKey("Bsc", "Gateway", { env: "prod" });
|
|
55
|
+
saveDeployment("Bsc", "Gateway", "0x...", { env: "prod" });
|
|
56
|
+
|
|
57
|
+
// Tron client
|
|
58
|
+
let client = TronClient.fromHre(hre);
|
|
59
|
+
let gw = await client.getContract(hre.artifacts, "Gateway", addr);
|
|
60
|
+
await gw.setWtoken(client.toHex(wtoken)).sendAndWait();
|
|
61
|
+
let val = await gw.wToken().call();
|
|
80
62
|
```
|
|
81
63
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
import { addressToHex, isTronAddress, isSolanaChain } from "@mapprotocol/common-contracts/utils/addressCodec";
|
|
86
|
-
|
|
87
|
-
// Auto-detect format and convert to hex
|
|
88
|
-
addressToHex("0xAbC..."); // EVM -> lowercase hex
|
|
89
|
-
addressToHex("TXyz..."); // Tron -> extract 20-byte address
|
|
90
|
-
addressToHex("So11111..."); // Solana -> full base58 decode
|
|
91
|
-
|
|
92
|
-
// Type checks
|
|
93
|
-
isTronAddress("TXyz..."); // true (validates 0x41 prefix byte)
|
|
94
|
-
isSolanaChain("Sol"); // true
|
|
95
|
-
```
|
|
64
|
+
See JSDoc in each module's source for full API details and edge cases.
|
|
96
65
|
|
|
97
66
|
## Forge Script Base
|
|
98
67
|
|
|
99
|
-
For monorepo projects, `script/Base.s.sol` provides deployment primitives:
|
|
100
|
-
|
|
101
68
|
```solidity
|
|
102
|
-
import {BaseScript} from "
|
|
69
|
+
import {BaseScript} from "@mapprotocol/common-contracts/script/base/Base.s.sol";
|
|
103
70
|
|
|
104
71
|
contract MyDeploy is BaseScript {
|
|
105
72
|
function run() public broadcast {
|
|
106
|
-
|
|
73
|
+
(address proxy, address impl) = deployProxy(type(MyContract).creationCode, initData);
|
|
107
74
|
address addr = deployByFactory("my_salt", type(MyContract).creationCode, abi.encode(arg));
|
|
108
|
-
|
|
109
|
-
// Check factory availability
|
|
110
|
-
require(isFactoryAvailable(), "no factory on this chain");
|
|
111
|
-
|
|
112
|
-
// UUPS proxy upgrade
|
|
113
|
-
upgradeProxy(proxyAddr, newImplAddr);
|
|
114
|
-
|
|
115
|
-
// Deploy new impl + upgrade in one step
|
|
116
75
|
deployAndUpgrade(proxyAddr, type(MyContractV2).creationCode);
|
|
76
|
+
address relay = readDeployment("Relay");
|
|
77
|
+
saveDeployment("Gateway", addr);
|
|
117
78
|
}
|
|
118
79
|
}
|
|
119
80
|
```
|
|
@@ -121,43 +82,12 @@ contract MyDeploy is BaseScript {
|
|
|
121
82
|
## Development
|
|
122
83
|
|
|
123
84
|
```bash
|
|
124
|
-
#
|
|
125
|
-
|
|
126
|
-
npm run build:
|
|
127
|
-
|
|
128
|
-
# Test
|
|
129
|
-
forge test
|
|
130
|
-
forge test --gas-report
|
|
131
|
-
|
|
132
|
-
# Format
|
|
133
|
-
forge fmt
|
|
134
|
-
|
|
135
|
-
# Publish
|
|
136
|
-
npm run prepublishOnly # clean + build + typecheck
|
|
137
|
-
npm publish
|
|
85
|
+
forge build && forge test # Solidity
|
|
86
|
+
npm run build:hardhat # Hardhat + TypeChain
|
|
87
|
+
npm run build:utils # TypeScript utilities
|
|
88
|
+
npm run prepublishOnly && npm publish
|
|
138
89
|
```
|
|
139
90
|
|
|
140
|
-
## Package Contents
|
|
141
|
-
|
|
142
|
-
| Path | Description |
|
|
143
|
-
|------|-------------|
|
|
144
|
-
| `contracts/**/*.sol` | Solidity source files |
|
|
145
|
-
| `artifacts/**/*.json` | Compiled ABI + bytecode |
|
|
146
|
-
| `typechain-types/**/*` | TypeChain generated types |
|
|
147
|
-
| `utils/*.ts` | Shared TypeScript utilities |
|
|
148
|
-
|
|
149
|
-
## Requirements
|
|
150
|
-
|
|
151
|
-
- Solidity ^0.8.20
|
|
152
|
-
- Node.js >= 18
|
|
153
|
-
- OpenZeppelin Contracts 5.4.0
|
|
154
|
-
|
|
155
91
|
## License
|
|
156
92
|
|
|
157
93
|
MIT
|
|
158
|
-
|
|
159
|
-
## Links
|
|
160
|
-
|
|
161
|
-
- [Repository](https://github.com/mapprotocol/mapo-contracts-v2/tree/main/common)
|
|
162
|
-
- [Issues](https://github.com/mapprotocol/mapo-contracts-v2/issues)
|
|
163
|
-
- [MAP Protocol](https://mapprotocol.io)
|
package/package.json
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mapprotocol/common-contracts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Common contracts for MAP Protocol",
|
|
5
5
|
"main": "typechain-types/index.js",
|
|
6
6
|
"types": "typechain-types/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./typechain-types/index.d.ts",
|
|
10
|
+
"default": "./typechain-types/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./utils/*": {
|
|
13
|
+
"types": "./utils/dist/*.d.ts",
|
|
14
|
+
"default": "./utils/dist/*.js"
|
|
15
|
+
},
|
|
16
|
+
"./contracts/*": "./contracts/*",
|
|
17
|
+
"./script/*": "./script/*"
|
|
18
|
+
},
|
|
7
19
|
"files": [
|
|
8
20
|
"contracts/**/*.sol",
|
|
9
21
|
"artifacts/contracts/**/*.json",
|
|
@@ -11,6 +23,7 @@
|
|
|
11
23
|
"typechain-types/**/*",
|
|
12
24
|
"utils/**/*.ts",
|
|
13
25
|
"utils/dist/**/*",
|
|
26
|
+
"script/base/**/*.sol",
|
|
14
27
|
"README.md"
|
|
15
28
|
],
|
|
16
29
|
"repository": {
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.20;
|
|
3
|
+
|
|
4
|
+
import {Script, stdJson, console} from "forge-std/Script.sol";
|
|
5
|
+
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
|
|
6
|
+
import {IFactory} from "./interfaces/IFactory.sol";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @title BaseScript
|
|
10
|
+
* @notice Base contract for deployment scripts with full lifecycle support
|
|
11
|
+
* @dev Inherit this in your deployment scripts.
|
|
12
|
+
*
|
|
13
|
+
* Features:
|
|
14
|
+
* - Private key management (mainnet / testnet)
|
|
15
|
+
* - Direct deployment and CREATE2 factory deployment
|
|
16
|
+
* - Proxy deployment (implementation + ERC1967Proxy)
|
|
17
|
+
* - UUPS proxy upgrade
|
|
18
|
+
* - deploy.json record read/write
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* contract MyDeploy is BaseScript {
|
|
22
|
+
* function run() public broadcast {
|
|
23
|
+
* // Direct deploy
|
|
24
|
+
* address impl = deployDirect(type(MyContract).creationCode, abi.encode(arg));
|
|
25
|
+
*
|
|
26
|
+
* // Factory deploy (deterministic address)
|
|
27
|
+
* address addr = deployByFactory("my_salt", type(MyContract).creationCode, abi.encode(arg));
|
|
28
|
+
*
|
|
29
|
+
* // Proxy deploy (impl + proxy)
|
|
30
|
+
* (address proxy, address impl) = deployProxy(type(MyContract).creationCode, initData);
|
|
31
|
+
* (address proxy, address impl) = deployProxyByFactory("my_salt", type(MyContract).creationCode, initData);
|
|
32
|
+
*
|
|
33
|
+
* // Upgrade
|
|
34
|
+
* upgradeProxy(proxy, newImplAddr);
|
|
35
|
+
* address newImpl = deployAndUpgrade(proxy, type(MyContractV2).creationCode);
|
|
36
|
+
*
|
|
37
|
+
* // Deploy record
|
|
38
|
+
* address relay = readDeployment("Relay");
|
|
39
|
+
* saveDeployment("Gateway", addr);
|
|
40
|
+
* }
|
|
41
|
+
* }
|
|
42
|
+
*/
|
|
43
|
+
abstract contract BaseScript is Script {
|
|
44
|
+
IFactory constant private FACTORY = IFactory(0x6258e4d2950757A749a4d4683A7342261ce12471);
|
|
45
|
+
using stdJson for string;
|
|
46
|
+
|
|
47
|
+
address internal broadcaster;
|
|
48
|
+
uint256 private broadcasterPK;
|
|
49
|
+
|
|
50
|
+
constructor() {
|
|
51
|
+
uint256 privateKey;
|
|
52
|
+
if (block.chainid == 212) {
|
|
53
|
+
privateKey = vm.envUint("TESTNET_PRIVATE_KEY");
|
|
54
|
+
} else {
|
|
55
|
+
privateKey = vm.envUint("PRIVATE_KEY");
|
|
56
|
+
}
|
|
57
|
+
broadcaster = vm.addr(privateKey);
|
|
58
|
+
broadcasterPK = privateKey;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
modifier broadcast() {
|
|
62
|
+
vm.startBroadcast(broadcasterPK);
|
|
63
|
+
_;
|
|
64
|
+
vm.stopBroadcast();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ============================================================
|
|
68
|
+
// Factory (CREATE2) deployment
|
|
69
|
+
// ============================================================
|
|
70
|
+
|
|
71
|
+
/// @notice Deploy contract via CREATE2 factory with deterministic address
|
|
72
|
+
function deployByFactory(
|
|
73
|
+
string memory salt,
|
|
74
|
+
bytes memory creationCode,
|
|
75
|
+
bytes memory constructorArgs
|
|
76
|
+
) internal returns (address addr) {
|
|
77
|
+
require(address(FACTORY).code.length > 0, "factory not deployed on this chain");
|
|
78
|
+
bytes32 saltHash = keccak256(bytes(salt));
|
|
79
|
+
addr = FACTORY.getAddress(saltHash);
|
|
80
|
+
if (addr.code.length > 0) revert("already deployed");
|
|
81
|
+
bytes memory code = abi.encodePacked(creationCode, constructorArgs);
|
|
82
|
+
FACTORY.deploy(saltHash, code, 0);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// @notice Deploy implementation + proxy via CREATE2 factory
|
|
86
|
+
function deployProxyByFactory(
|
|
87
|
+
string memory salt,
|
|
88
|
+
bytes memory implCreationCode,
|
|
89
|
+
bytes memory initData
|
|
90
|
+
) internal returns (address proxy, address impl) {
|
|
91
|
+
impl = deployDirect(implCreationCode, bytes(""));
|
|
92
|
+
proxy = deployByFactory(salt, type(ERC1967Proxy).creationCode, abi.encode(impl, initData));
|
|
93
|
+
console.log("Proxy:", proxy);
|
|
94
|
+
console.log("Implementation:", impl);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// @notice Check if the CREATE2 factory is available on this chain
|
|
98
|
+
function isFactoryAvailable() internal view returns (bool) {
|
|
99
|
+
return address(FACTORY).code.length > 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// @notice Get the predicted address for a given salt
|
|
103
|
+
function getFactoryAddress(string memory salt) internal view returns (address) {
|
|
104
|
+
require(isFactoryAvailable(), "factory not deployed on this chain");
|
|
105
|
+
return FACTORY.getAddress(keccak256(bytes(salt)));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// @notice Check if a contract is already deployed at the factory address
|
|
109
|
+
function isFactoryDeployed(string memory salt) internal view returns (bool) {
|
|
110
|
+
if (!isFactoryAvailable()) return false;
|
|
111
|
+
return FACTORY.getAddress(keccak256(bytes(salt))).code.length > 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ============================================================
|
|
115
|
+
// Direct deployment
|
|
116
|
+
// ============================================================
|
|
117
|
+
|
|
118
|
+
/// @notice Deploy a new contract directly (non-deterministic address)
|
|
119
|
+
function deployDirect(
|
|
120
|
+
bytes memory creationCode,
|
|
121
|
+
bytes memory constructorArgs
|
|
122
|
+
) internal returns (address addr) {
|
|
123
|
+
bytes memory code = abi.encodePacked(creationCode, constructorArgs);
|
|
124
|
+
assembly {
|
|
125
|
+
addr := create(0, add(code, 0x20), mload(code))
|
|
126
|
+
}
|
|
127
|
+
require(addr != address(0), "deploy failed");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// @notice Deploy implementation + proxy directly
|
|
131
|
+
function deployProxy(
|
|
132
|
+
bytes memory implCreationCode,
|
|
133
|
+
bytes memory initData
|
|
134
|
+
) internal returns (address proxy, address impl) {
|
|
135
|
+
impl = deployDirect(implCreationCode, bytes(""));
|
|
136
|
+
ERC1967Proxy p = new ERC1967Proxy(impl, initData);
|
|
137
|
+
proxy = address(p);
|
|
138
|
+
console.log("Proxy:", proxy);
|
|
139
|
+
console.log("Implementation:", impl);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ============================================================
|
|
143
|
+
// Upgrade
|
|
144
|
+
// ============================================================
|
|
145
|
+
|
|
146
|
+
/// @notice Upgrade a UUPS proxy to a new implementation
|
|
147
|
+
function upgradeProxy(address proxy, address newImpl) internal {
|
|
148
|
+
address oldImpl = _getImplementation(proxy);
|
|
149
|
+
(bool success,) = proxy.call(
|
|
150
|
+
abi.encodeWithSignature("upgradeToAndCall(address,bytes)", newImpl, bytes(""))
|
|
151
|
+
);
|
|
152
|
+
require(success, "upgrade failed");
|
|
153
|
+
console.log("Upgraded:", oldImpl, "->", newImpl);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/// @notice Deploy new implementation and upgrade proxy in one step
|
|
157
|
+
function deployAndUpgrade(address proxy, bytes memory implCreationCode) internal returns (address newImpl) {
|
|
158
|
+
newImpl = deployDirect(implCreationCode, bytes(""));
|
|
159
|
+
upgradeProxy(proxy, newImpl);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ============================================================
|
|
163
|
+
// Deploy record (deploy.json) read/write
|
|
164
|
+
// ============================================================
|
|
165
|
+
|
|
166
|
+
/// @notice Read a deployed address from deployments/deploy.json
|
|
167
|
+
function readDeployment(string memory key) internal view returns (address) {
|
|
168
|
+
(string memory env, string memory chain) = _resolveDeploymentPath();
|
|
169
|
+
return _readDeploymentByPath(env, chain, key);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/// @notice Read a deployed address for a specific env and chain
|
|
173
|
+
function _readDeploymentByPath(string memory env, string memory chain, string memory key) internal view returns (address addr) {
|
|
174
|
+
string memory filePath = "deployments/deploy.json";
|
|
175
|
+
if (!vm.exists(filePath)) {
|
|
176
|
+
revert(string(abi.encodePacked("deploy.json not found: ", filePath)));
|
|
177
|
+
}
|
|
178
|
+
string memory json = vm.readFile(filePath);
|
|
179
|
+
// JSON path: .prod.Bsc.Gateway
|
|
180
|
+
string memory jsonPath = string(abi.encodePacked(".", env, ".", chain, ".", key));
|
|
181
|
+
addr = json.readAddress(jsonPath);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/// @notice Save a deployed address to deployments/deploy.json
|
|
185
|
+
function saveDeployment(string memory key, address addr) internal {
|
|
186
|
+
(string memory env, string memory chain) = _resolveDeploymentPath();
|
|
187
|
+
string memory filePath = "deployments/deploy.json";
|
|
188
|
+
// JSON path: .prod.Bsc.Gateway
|
|
189
|
+
string memory jsonPath = string(abi.encodePacked(".", env, ".", chain, ".", key));
|
|
190
|
+
string memory json = vm.readFile(filePath);
|
|
191
|
+
bool exists = vm.keyExistsJson(json, jsonPath);
|
|
192
|
+
if (!exists) {
|
|
193
|
+
revert(string(abi.encodePacked("key not found: ", env, ".", chain, ".", key)));
|
|
194
|
+
}
|
|
195
|
+
vm.writeJson(vm.toString(addr), filePath, jsonPath);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ============================================================
|
|
199
|
+
// Internal helpers
|
|
200
|
+
// ============================================================
|
|
201
|
+
|
|
202
|
+
/// @notice Resolve deployment path (env, chain) based on chainId and NETWORK_ENV
|
|
203
|
+
/// @return env "prod", "main", or "test"
|
|
204
|
+
/// @return chain "Mapo", "Bsc", etc.
|
|
205
|
+
function _resolveDeploymentPath() internal view returns (string memory env, string memory chain) {
|
|
206
|
+
uint256 chainId = block.chainid;
|
|
207
|
+
env = vm.envString("NETWORK_ENV"); // required: test/prod/main
|
|
208
|
+
|
|
209
|
+
if (chainId == 212 || chainId == 22776) chain = "Mapo";
|
|
210
|
+
else if (chainId == 11155111 || chainId == 1) chain = "Eth";
|
|
211
|
+
else if (chainId == 97 || chainId == 56) chain = "Bsc";
|
|
212
|
+
else if (chainId == 8453) chain = "Base";
|
|
213
|
+
else if (chainId == 42161) chain = "Arb";
|
|
214
|
+
else if (chainId == 10) chain = "Op";
|
|
215
|
+
else if (chainId == 130) chain = "Uni";
|
|
216
|
+
else if (chainId == 137) chain = "Pol";
|
|
217
|
+
else if (chainId == 196) chain = "Xlayer";
|
|
218
|
+
else revert("unknown chain");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/// @notice Read current implementation address from a UUPS proxy
|
|
222
|
+
function _getImplementation(address proxy) internal view returns (address) {
|
|
223
|
+
(bool success, bytes memory data) = proxy.staticcall(
|
|
224
|
+
abi.encodeWithSignature("getImplementation()")
|
|
225
|
+
);
|
|
226
|
+
if (success && data.length == 32) {
|
|
227
|
+
return abi.decode(data, (address));
|
|
228
|
+
}
|
|
229
|
+
return address(0);
|
|
230
|
+
}
|
|
231
|
+
}
|
package/utils/deployer.ts
CHANGED
|
@@ -13,12 +13,17 @@ import { verify as _verify } from "./verifier";
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
export interface DeployerOptions {
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Auto-verify contracts after deploy/upgrade.
|
|
18
|
+
* Failures are logged as warnings, never thrown — call deployer.verify() for explicit error handling.
|
|
19
|
+
* Defaults to false.
|
|
20
|
+
*/
|
|
21
|
+
autoVerify?: boolean;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
export interface DeployResult {
|
|
20
|
-
address: string; //
|
|
21
|
-
hex?: string; // 0x hex
|
|
25
|
+
address: string; // EVM: 0x hex address. Tron: base58 (T...) address.
|
|
26
|
+
hex?: string; // Tron only: 0x hex address. Undefined on EVM.
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
export interface DeployProxyResult {
|
|
@@ -29,9 +34,16 @@ export interface DeployProxyResult {
|
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
export interface Deployer {
|
|
37
|
+
/**
|
|
38
|
+
* Deploy a contract.
|
|
39
|
+
* @param contractName - artifact name
|
|
40
|
+
* @param args - constructor arguments as raw values
|
|
41
|
+
* @param salt - "" or omitted = direct deploy; non-empty string = CREATE2 factory (deterministic address)
|
|
42
|
+
*/
|
|
32
43
|
deploy(contractName: string, args?: any[], salt?: string): Promise<DeployResult>;
|
|
33
44
|
deployProxy(contractName: string, initArgs?: any[], salt?: string): Promise<DeployProxyResult>;
|
|
34
45
|
upgrade(contractName: string, proxyAddr: string): Promise<DeployResult>;
|
|
46
|
+
/** Verify explicitly (throws on failure, unlike autoVerify which only warns). */
|
|
35
47
|
verify(contractName: string, address: string, constructorArgs?: any[], contractPath?: string): Promise<void>;
|
|
36
48
|
isTron: boolean;
|
|
37
49
|
network: string;
|
package/utils/dist/deployer.d.ts
CHANGED
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
* let newImpl = await deployer.upgrade("Gateway", proxyAddr);
|
|
9
9
|
*/
|
|
10
10
|
export interface DeployerOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Auto-verify contracts after deploy/upgrade.
|
|
13
|
+
* Failures are logged as warnings, never thrown — call deployer.verify() for explicit error handling.
|
|
14
|
+
* Defaults to false.
|
|
15
|
+
*/
|
|
11
16
|
autoVerify?: boolean;
|
|
12
17
|
}
|
|
13
18
|
export interface DeployResult {
|
|
@@ -21,9 +26,16 @@ export interface DeployProxyResult {
|
|
|
21
26
|
implementationHex?: string;
|
|
22
27
|
}
|
|
23
28
|
export interface Deployer {
|
|
29
|
+
/**
|
|
30
|
+
* Deploy a contract.
|
|
31
|
+
* @param contractName - artifact name
|
|
32
|
+
* @param args - constructor arguments as raw values
|
|
33
|
+
* @param salt - "" or omitted = direct deploy; non-empty string = CREATE2 factory (deterministic address)
|
|
34
|
+
*/
|
|
24
35
|
deploy(contractName: string, args?: any[], salt?: string): Promise<DeployResult>;
|
|
25
36
|
deployProxy(contractName: string, initArgs?: any[], salt?: string): Promise<DeployProxyResult>;
|
|
26
37
|
upgrade(contractName: string, proxyAddr: string): Promise<DeployResult>;
|
|
38
|
+
/** Verify explicitly (throws on failure, unlike autoVerify which only warns). */
|
|
27
39
|
verify(contractName: string, address: string, constructorArgs?: any[], contractPath?: string): Promise<void>;
|
|
28
40
|
isTron: boolean;
|
|
29
41
|
network: string;
|
package/utils/dist/verifier.d.ts
CHANGED
|
@@ -2,7 +2,17 @@ export interface VerifyOptions {
|
|
|
2
2
|
address: string;
|
|
3
3
|
contractName: string;
|
|
4
4
|
contractPath?: string;
|
|
5
|
+
/**
|
|
6
|
+
* Constructor arguments as raw values — auto-encoded via ethers `encodeDeploy`.
|
|
7
|
+
* Example: ["0xAbC123...", "0xDeF456...", 200]
|
|
8
|
+
* Use this for most cases. Mutually exclusive with constructorParams.
|
|
9
|
+
*/
|
|
5
10
|
constructorArgs?: any[];
|
|
11
|
+
/**
|
|
12
|
+
* Pre-encoded constructor params as hex string (without 0x prefix).
|
|
13
|
+
* Use this when you already have the ABI-encoded bytes, e.g. from `cast abi-encode`.
|
|
14
|
+
* Takes priority over constructorArgs if both are provided.
|
|
15
|
+
*/
|
|
6
16
|
constructorParams?: string;
|
|
7
17
|
compiler?: string;
|
|
8
18
|
optimizer?: boolean;
|
package/utils/dist/verifier.js
CHANGED
|
@@ -56,13 +56,13 @@ async function verifyTron(hre, opts) {
|
|
|
56
56
|
const chainId = hre.network.config.chainId;
|
|
57
57
|
const fs = require("fs");
|
|
58
58
|
const path = require("path");
|
|
59
|
-
//
|
|
59
|
+
// Compiler settings: opts > build-info > hardhat config (no hardcoded defaults)
|
|
60
60
|
const solcConfig = hre.config?.solidity?.compilers?.[0] || hre.config?.solidity || {};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
let compiler = opts.compiler || solcConfig.version || "";
|
|
62
|
+
let optimizer = opts.optimizer ?? solcConfig.settings?.optimizer?.enabled;
|
|
63
|
+
let optimizerRuns = opts.optimizerRuns ?? solcConfig.settings?.optimizer?.runs;
|
|
64
|
+
let evmVersion = solcConfig.settings?.evmVersion || "";
|
|
65
|
+
let viaIR = solcConfig.settings?.viaIR ? "1" : "0";
|
|
66
66
|
// Convert address to Tron format
|
|
67
67
|
let address = opts.address;
|
|
68
68
|
if (address.startsWith("0x")) {
|
|
@@ -71,24 +71,23 @@ async function verifyTron(hre, opts) {
|
|
|
71
71
|
// Generate flattened source
|
|
72
72
|
const outputDir = path.join(process.cwd(), "verify-output");
|
|
73
73
|
const flattenPath = path.join(outputDir, `${opts.contractName}_flatten.sol`);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
// Find actual source path from artifact
|
|
77
|
-
const artifact = await hre.artifacts.readArtifact(opts.contractName);
|
|
78
|
-
const sourcePath = artifact.sourceName; // e.g. "contracts/factory/Create2Factory.sol"
|
|
79
|
-
let flattenedSource = await hre.run("flatten:get-flattened-sources", {
|
|
80
|
-
files: [sourcePath],
|
|
81
|
-
});
|
|
82
|
-
flattenedSource = removeDuplicateSPDX(flattenedSource);
|
|
83
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
84
|
-
fs.writeFileSync(flattenPath, flattenedSource);
|
|
85
|
-
console.log(`flatten saved to: ${flattenPath}`);
|
|
74
|
+
if (fs.existsSync(flattenPath)) {
|
|
75
|
+
console.log(`using existing flatten: ${flattenPath}`);
|
|
86
76
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
77
|
+
else {
|
|
78
|
+
console.log(`generating flatten for ${opts.contractName}...`);
|
|
79
|
+
try {
|
|
80
|
+
const artifact = await hre.artifacts.readArtifact(opts.contractName);
|
|
81
|
+
const sourcePath = artifact.sourceName;
|
|
82
|
+
let flattenedSource = await hre.run("flatten:get-flattened-sources", {
|
|
83
|
+
files: [sourcePath],
|
|
84
|
+
});
|
|
85
|
+
flattenedSource = removeDuplicateSPDX(flattenedSource);
|
|
86
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
87
|
+
fs.writeFileSync(flattenPath, flattenedSource);
|
|
88
|
+
console.log(`flatten saved to: ${flattenPath}`);
|
|
90
89
|
}
|
|
91
|
-
|
|
90
|
+
catch (e) {
|
|
92
91
|
console.log(`flatten failed, generate manually: forge flatten contracts/${opts.contractName}.sol`);
|
|
93
92
|
return;
|
|
94
93
|
}
|
|
@@ -106,27 +105,49 @@ async function verifyTron(hre, opts) {
|
|
|
106
105
|
const form = new FormData();
|
|
107
106
|
form.append("contractAddress", address);
|
|
108
107
|
form.append("contractName", opts.contractName);
|
|
109
|
-
// Read
|
|
108
|
+
// Read compiler settings from build-info that contains this contract
|
|
110
109
|
let fullCompiler = `v${compiler}`;
|
|
111
110
|
try {
|
|
112
111
|
const buildInfoDir = path.join(process.cwd(), "artifacts/build-info");
|
|
113
|
-
const buildInfoFiles = fs.readdirSync(buildInfoDir);
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
112
|
+
const buildInfoFiles = fs.readdirSync(buildInfoDir).filter((f) => f.endsWith(".json"));
|
|
113
|
+
const artifact = await hre.artifacts.readArtifact(opts.contractName);
|
|
114
|
+
const sourceName = artifact.sourceName;
|
|
115
|
+
for (const file of buildInfoFiles) {
|
|
116
|
+
const buildInfo = JSON.parse(fs.readFileSync(path.join(buildInfoDir, file), "utf-8"));
|
|
117
|
+
if (buildInfo.output?.contracts?.[sourceName]?.[opts.contractName]) {
|
|
118
|
+
if (buildInfo.solcLongVersion) {
|
|
119
|
+
fullCompiler = `v${buildInfo.solcLongVersion}`;
|
|
120
|
+
}
|
|
121
|
+
// Override settings from actual build-info input
|
|
122
|
+
const settings = buildInfo.input?.settings;
|
|
123
|
+
if (settings) {
|
|
124
|
+
if (settings.evmVersion)
|
|
125
|
+
evmVersion = settings.evmVersion;
|
|
126
|
+
if (settings.optimizer != null) {
|
|
127
|
+
optimizer = settings.optimizer.enabled ?? optimizer;
|
|
128
|
+
optimizerRuns = settings.optimizer.runs ?? optimizerRuns;
|
|
129
|
+
}
|
|
130
|
+
if (settings.viaIR != null)
|
|
131
|
+
viaIR = settings.viaIR ? "1" : "0";
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
118
134
|
}
|
|
119
135
|
}
|
|
120
136
|
}
|
|
121
137
|
catch (e) {
|
|
122
138
|
console.log(`[warn] could not read build-info: ${e.message}`);
|
|
123
139
|
}
|
|
140
|
+
if (!fullCompiler.includes("+commit.")) {
|
|
141
|
+
console.log(`[error] could not determine full compiler version (got: ${fullCompiler}). Run 'npx hardhat compile' to generate build-info.`);
|
|
142
|
+
printTronVerifyInfo(address, opts.contractName, fullCompiler, !!optimizer, optimizerRuns || 200, evmVersion || "london", flattenPath, chainId);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
124
145
|
form.append("compiler", fullCompiler);
|
|
125
146
|
form.append("license", "3"); // MIT
|
|
126
147
|
form.append("optimizer", optimizer ? "1" : "0");
|
|
127
|
-
form.append("runs", String(optimizerRuns));
|
|
148
|
+
form.append("runs", String(optimizerRuns ?? 200));
|
|
128
149
|
form.append("viaIR", viaIR);
|
|
129
|
-
form.append("evmVersion", evmVersion);
|
|
150
|
+
form.append("evmVersion", evmVersion || "london");
|
|
130
151
|
// Encode constructor params from ABI if not pre-encoded
|
|
131
152
|
let constructorParams = opts.constructorParams || "";
|
|
132
153
|
if (!constructorParams && opts.constructorArgs && opts.constructorArgs.length > 0) {
|
|
@@ -164,11 +185,14 @@ async function verifyTron(hre, opts) {
|
|
|
164
185
|
req.on("error", reject);
|
|
165
186
|
form.pipe(req);
|
|
166
187
|
});
|
|
167
|
-
|
|
168
|
-
|
|
188
|
+
console.log(`TronScan raw response:`, JSON.stringify(result, null, 2));
|
|
189
|
+
const status = result.data?.status;
|
|
190
|
+
if ((result.code === 200 && (status === 200 || status === 2006)) || result.success) {
|
|
191
|
+
console.log(`${opts.contractName} verified on TronScan`);
|
|
169
192
|
}
|
|
170
193
|
else {
|
|
171
|
-
console.log(`
|
|
194
|
+
console.log(`verification failed (status: ${status}): ${result.data?.message || "unknown"}`);
|
|
195
|
+
console.log(` compiler: ${fullCompiler}, evmVersion: ${evmVersion}, optimizer: ${optimizer}, runs: ${optimizerRuns}, viaIR: ${viaIR}`);
|
|
172
196
|
printTronVerifyInfo(address, opts.contractName, fullCompiler, optimizer, optimizerRuns, evmVersion, flattenPath, chainId);
|
|
173
197
|
}
|
|
174
198
|
}
|
package/utils/verifier.ts
CHANGED
|
@@ -11,14 +11,24 @@ const TRONSCAN_API: Record<number, string> = {
|
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
export interface VerifyOptions {
|
|
14
|
-
address: string; // contract address
|
|
14
|
+
address: string; // contract address (0x hex or Tron base58)
|
|
15
15
|
contractName: string; // e.g. "AuthorityManager"
|
|
16
16
|
contractPath?: string; // e.g. "contracts/AuthorityManager.sol:AuthorityManager"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Constructor arguments as raw values — auto-encoded via ethers `encodeDeploy`.
|
|
19
|
+
* Example: ["0xAbC123...", "0xDeF456...", 200]
|
|
20
|
+
* Use this for most cases. Mutually exclusive with constructorParams.
|
|
21
|
+
*/
|
|
22
|
+
constructorArgs?: any[];
|
|
23
|
+
/**
|
|
24
|
+
* Pre-encoded constructor params as hex string (without 0x prefix).
|
|
25
|
+
* Use this when you already have the ABI-encoded bytes, e.g. from `cast abi-encode`.
|
|
26
|
+
* Takes priority over constructorArgs if both are provided.
|
|
27
|
+
*/
|
|
28
|
+
constructorParams?: string;
|
|
29
|
+
compiler?: string; // override solc version (auto-read from build-info if omitted)
|
|
30
|
+
optimizer?: boolean; // override optimizer enabled (auto-read from build-info if omitted)
|
|
31
|
+
optimizerRuns?: number; // override optimizer runs (auto-read from build-info if omitted)
|
|
22
32
|
}
|
|
23
33
|
|
|
24
34
|
/**
|
|
@@ -71,13 +81,13 @@ async function verifyTron(hre: any, opts: VerifyOptions): Promise<void> {
|
|
|
71
81
|
const fs = require("fs");
|
|
72
82
|
const path = require("path");
|
|
73
83
|
|
|
74
|
-
//
|
|
84
|
+
// Compiler settings: opts > build-info > hardhat config (no hardcoded defaults)
|
|
75
85
|
const solcConfig = hre.config?.solidity?.compilers?.[0] || hre.config?.solidity || {};
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
86
|
+
let compiler = opts.compiler || solcConfig.version || "";
|
|
87
|
+
let optimizer = opts.optimizer ?? solcConfig.settings?.optimizer?.enabled;
|
|
88
|
+
let optimizerRuns = opts.optimizerRuns ?? solcConfig.settings?.optimizer?.runs;
|
|
89
|
+
let evmVersion = solcConfig.settings?.evmVersion || "";
|
|
90
|
+
let viaIR = solcConfig.settings?.viaIR ? "1" : "0";
|
|
81
91
|
|
|
82
92
|
// Convert address to Tron format
|
|
83
93
|
let address = opts.address;
|
|
@@ -89,22 +99,21 @@ async function verifyTron(hre: any, opts: VerifyOptions): Promise<void> {
|
|
|
89
99
|
const outputDir = path.join(process.cwd(), "verify-output");
|
|
90
100
|
const flattenPath = path.join(outputDir, `${opts.contractName}_flatten.sol`);
|
|
91
101
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
} else {
|
|
102
|
+
if (fs.existsSync(flattenPath)) {
|
|
103
|
+
console.log(`using existing flatten: ${flattenPath}`);
|
|
104
|
+
} else {
|
|
105
|
+
console.log(`generating flatten for ${opts.contractName}...`);
|
|
106
|
+
try {
|
|
107
|
+
const artifact = await hre.artifacts.readArtifact(opts.contractName);
|
|
108
|
+
const sourcePath = artifact.sourceName;
|
|
109
|
+
let flattenedSource = await hre.run("flatten:get-flattened-sources", {
|
|
110
|
+
files: [sourcePath],
|
|
111
|
+
});
|
|
112
|
+
flattenedSource = removeDuplicateSPDX(flattenedSource);
|
|
113
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
114
|
+
fs.writeFileSync(flattenPath, flattenedSource);
|
|
115
|
+
console.log(`flatten saved to: ${flattenPath}`);
|
|
116
|
+
} catch (e) {
|
|
108
117
|
console.log(`flatten failed, generate manually: forge flatten contracts/${opts.contractName}.sol`);
|
|
109
118
|
return;
|
|
110
119
|
}
|
|
@@ -124,28 +133,47 @@ async function verifyTron(hre: any, opts: VerifyOptions): Promise<void> {
|
|
|
124
133
|
const form = new FormData();
|
|
125
134
|
form.append("contractAddress", address);
|
|
126
135
|
form.append("contractName", opts.contractName);
|
|
127
|
-
// Read
|
|
136
|
+
// Read compiler settings from build-info that contains this contract
|
|
128
137
|
let fullCompiler = `v${compiler}`;
|
|
129
138
|
try {
|
|
130
139
|
const buildInfoDir = path.join(process.cwd(), "artifacts/build-info");
|
|
131
|
-
const buildInfoFiles = fs.readdirSync(buildInfoDir);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
140
|
+
const buildInfoFiles = fs.readdirSync(buildInfoDir).filter((f: string) => f.endsWith(".json"));
|
|
141
|
+
const artifact = await hre.artifacts.readArtifact(opts.contractName);
|
|
142
|
+
const sourceName = artifact.sourceName;
|
|
143
|
+
|
|
144
|
+
for (const file of buildInfoFiles) {
|
|
145
|
+
const buildInfo = JSON.parse(fs.readFileSync(path.join(buildInfoDir, file), "utf-8"));
|
|
146
|
+
if (buildInfo.output?.contracts?.[sourceName]?.[opts.contractName]) {
|
|
147
|
+
if (buildInfo.solcLongVersion) {
|
|
148
|
+
fullCompiler = `v${buildInfo.solcLongVersion}`;
|
|
149
|
+
}
|
|
150
|
+
// Override settings from actual build-info input
|
|
151
|
+
const settings = buildInfo.input?.settings;
|
|
152
|
+
if (settings) {
|
|
153
|
+
if (settings.evmVersion) evmVersion = settings.evmVersion;
|
|
154
|
+
if (settings.optimizer != null) {
|
|
155
|
+
optimizer = settings.optimizer.enabled ?? optimizer;
|
|
156
|
+
optimizerRuns = settings.optimizer.runs ?? optimizerRuns;
|
|
157
|
+
}
|
|
158
|
+
if (settings.viaIR != null) viaIR = settings.viaIR ? "1" : "0";
|
|
159
|
+
}
|
|
160
|
+
break;
|
|
138
161
|
}
|
|
139
162
|
}
|
|
140
163
|
} catch (e: any) {
|
|
141
164
|
console.log(`[warn] could not read build-info: ${e.message}`);
|
|
142
165
|
}
|
|
166
|
+
if (!fullCompiler.includes("+commit.")) {
|
|
167
|
+
console.log(`[error] could not determine full compiler version (got: ${fullCompiler}). Run 'npx hardhat compile' to generate build-info.`);
|
|
168
|
+
printTronVerifyInfo(address, opts.contractName, fullCompiler, !!optimizer, optimizerRuns || 200, evmVersion || "london", flattenPath, chainId);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
143
171
|
form.append("compiler", fullCompiler);
|
|
144
172
|
form.append("license", "3"); // MIT
|
|
145
173
|
form.append("optimizer", optimizer ? "1" : "0");
|
|
146
|
-
form.append("runs", String(optimizerRuns));
|
|
174
|
+
form.append("runs", String(optimizerRuns ?? 200));
|
|
147
175
|
form.append("viaIR", viaIR);
|
|
148
|
-
form.append("evmVersion", evmVersion);
|
|
176
|
+
form.append("evmVersion", evmVersion || "london");
|
|
149
177
|
// Encode constructor params from ABI if not pre-encoded
|
|
150
178
|
let constructorParams = opts.constructorParams || "";
|
|
151
179
|
if (!constructorParams && opts.constructorArgs && opts.constructorArgs.length > 0) {
|
|
@@ -180,10 +208,13 @@ async function verifyTron(hre: any, opts: VerifyOptions): Promise<void> {
|
|
|
180
208
|
form.pipe(req);
|
|
181
209
|
});
|
|
182
210
|
|
|
183
|
-
|
|
184
|
-
|
|
211
|
+
console.log(`TronScan raw response:`, JSON.stringify(result, null, 2));
|
|
212
|
+
const status = result.data?.status;
|
|
213
|
+
if ((result.code === 200 && (status === 200 || status === 2006)) || result.success) {
|
|
214
|
+
console.log(`${opts.contractName} verified on TronScan`);
|
|
185
215
|
} else {
|
|
186
|
-
console.log(`
|
|
216
|
+
console.log(`verification failed (status: ${status}): ${result.data?.message || "unknown"}`);
|
|
217
|
+
console.log(` compiler: ${fullCompiler}, evmVersion: ${evmVersion}, optimizer: ${optimizer}, runs: ${optimizerRuns}, viaIR: ${viaIR}`);
|
|
187
218
|
printTronVerifyInfo(address, opts.contractName, fullCompiler, optimizer, optimizerRuns, evmVersion, flattenPath, chainId);
|
|
188
219
|
}
|
|
189
220
|
} catch (e: any) {
|