@mapprotocol/common-contracts 0.2.0 → 0.3.1
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/artifacts/contracts/factory/Create2Factory.sol/Create2Factory.json +107 -0
- package/contracts/factory/Create2Factory.sol +57 -0
- package/package.json +4 -1
- package/typechain-types/contracts/factory/Create2Factory.ts +174 -0
- package/typechain-types/contracts/factory/index.ts +4 -0
- package/typechain-types/contracts/index.ts +2 -0
- package/typechain-types/factories/contracts/factory/Create2Factory__factory.ts +163 -0
- package/typechain-types/factories/contracts/factory/index.ts +4 -0
- package/typechain-types/factories/contracts/index.ts +1 -0
- package/typechain-types/hardhat.d.ts +18 -0
- package/typechain-types/index.ts +2 -0
- package/utils/addressCodec.ts +12 -7
- package/utils/codeHash.ts +75 -0
- package/utils/deployRecord.ts +117 -0
- package/utils/deployer.ts +123 -0
- package/utils/evmHelper.ts +108 -0
- package/utils/factory.ts +210 -0
- package/utils/index.ts +35 -18
- package/utils/tronHelper.ts +312 -30
- package/utils/verifier.ts +225 -0
- package/utils/deployment.ts +0 -81
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculate creationCode hash for CREATE2 address prediction.
|
|
3
|
+
*/
|
|
4
|
+
const { keccak256, Interface } = require("ethers");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Get codeHash for a contract deployment.
|
|
8
|
+
* @param artifacts - hardhat artifacts (hre.artifacts)
|
|
9
|
+
* @param contractName - contract name (e.g. "Gateway", "AuthorityManager")
|
|
10
|
+
* @param args - constructor arguments array
|
|
11
|
+
*/
|
|
12
|
+
export async function getCodeHash(artifacts: any, contractName: string, args: any[] = []): Promise<string> {
|
|
13
|
+
const artifact = await artifacts.readArtifact(contractName);
|
|
14
|
+
let bytecode = artifact.bytecode;
|
|
15
|
+
if (!bytecode.startsWith("0x")) bytecode = "0x" + bytecode;
|
|
16
|
+
|
|
17
|
+
if (args.length > 0) {
|
|
18
|
+
const iface = new Interface(artifact.abi);
|
|
19
|
+
const encoded = iface.encodeDeploy(args);
|
|
20
|
+
return keccak256(bytecode + encoded.slice(2));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return keccak256(bytecode);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get codeHash for an ERC1967Proxy deployment.
|
|
28
|
+
* @param artifacts - hardhat artifacts (hre.artifacts)
|
|
29
|
+
* @param implAddress - implementation address
|
|
30
|
+
* @param implName - implementation contract name (for encoding initData)
|
|
31
|
+
* @param initArgs - arguments for initialize() function
|
|
32
|
+
*/
|
|
33
|
+
export async function getProxyCodeHash(
|
|
34
|
+
artifacts: any,
|
|
35
|
+
implAddress: string,
|
|
36
|
+
implName: string,
|
|
37
|
+
initArgs: any[] = []
|
|
38
|
+
): Promise<string> {
|
|
39
|
+
return getCustomProxyCodeHash(artifacts, "ERC1967Proxy", implAddress, implName, initArgs);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get codeHash for a custom proxy deployment.
|
|
44
|
+
* @param artifacts - hardhat artifacts (hre.artifacts)
|
|
45
|
+
* @param proxyName - proxy contract name (e.g. "MyCustomProxy")
|
|
46
|
+
* @param implAddress - implementation address
|
|
47
|
+
* @param implName - implementation contract name (for encoding initData)
|
|
48
|
+
* @param initArgs - arguments for initialize() function
|
|
49
|
+
*/
|
|
50
|
+
export async function getCustomProxyCodeHash(
|
|
51
|
+
artifacts: any,
|
|
52
|
+
proxyName: string,
|
|
53
|
+
implAddress: string,
|
|
54
|
+
implName: string,
|
|
55
|
+
initArgs: any[] = []
|
|
56
|
+
): Promise<string> {
|
|
57
|
+
const proxyArtifact = await artifacts.readArtifact(proxyName);
|
|
58
|
+
let bytecode = proxyArtifact.bytecode;
|
|
59
|
+
if (!bytecode.startsWith("0x")) bytecode = "0x" + bytecode;
|
|
60
|
+
|
|
61
|
+
let initData = "0x";
|
|
62
|
+
if (initArgs.length > 0) {
|
|
63
|
+
const implArtifact = await artifacts.readArtifact(implName);
|
|
64
|
+
const iface = new Interface(implArtifact.abi);
|
|
65
|
+
initData = iface.encodeFunctionData("initialize", initArgs);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const { AbiCoder } = require("ethers");
|
|
69
|
+
const constructorArgs = AbiCoder.defaultAbiCoder().encode(
|
|
70
|
+
["address", "bytes"],
|
|
71
|
+
[implAddress, initData]
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return keccak256(bytecode + constructorArgs.slice(2));
|
|
75
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deploy record — read/write deployed contract addresses from deploy.json
|
|
3
|
+
*
|
|
4
|
+
* Structure:
|
|
5
|
+
* {
|
|
6
|
+
* "prod": { "Mapo": { "Relay": "0x..." }, "Bsc": { "Gateway": "0x..." } },
|
|
7
|
+
* "main": { "Mapo": { "Relay": "0x..." } },
|
|
8
|
+
* "test": { "Mapo": { "Relay": "0x..." } }
|
|
9
|
+
* }
|
|
10
|
+
*/
|
|
11
|
+
let fs = require("fs");
|
|
12
|
+
let path = require("path");
|
|
13
|
+
|
|
14
|
+
type DeployData = {
|
|
15
|
+
[env: string]: {
|
|
16
|
+
[chain: string]: {
|
|
17
|
+
[key: string]: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export interface DeploymentPath {
|
|
23
|
+
env: string; // "prod", "main", "test"
|
|
24
|
+
chain: string; // "Mapo", "Bsc", "Tron"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface DeploymentOptions {
|
|
28
|
+
basePath?: string; // defaults to <cwd>/deployments/
|
|
29
|
+
env: string; // "prod", "main", "test" — required
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function defaultDeployPath(): string {
|
|
33
|
+
return path.join(process.cwd(), "deployments");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Resolve deployment path from network name and env.
|
|
38
|
+
* @param network - hardhat network name (e.g. "Bsc", "Mapo_test", "Tron")
|
|
39
|
+
* @param env - deployment environment: "prod", "main", "test"
|
|
40
|
+
* @returns { env, chain } for deploy.json lookup
|
|
41
|
+
*/
|
|
42
|
+
export function resolveDeploymentPath(network: string, env: string): DeploymentPath {
|
|
43
|
+
// Strip _test suffix from network name to get chain name
|
|
44
|
+
const chain = network.replace(/_?test$/i, "");
|
|
45
|
+
return { env, chain: chain.charAt(0).toUpperCase() + chain.slice(1) };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Read a deployed contract address from deploy.json.
|
|
50
|
+
* @param network - hardhat network name
|
|
51
|
+
* @param key - contract key (e.g. "Gateway", "Authority")
|
|
52
|
+
* @param opts - optional basePath and env overrides
|
|
53
|
+
*/
|
|
54
|
+
export async function getDeploymentByKey(network: string, key: string, opts: DeploymentOptions): Promise<string> {
|
|
55
|
+
const deployPath = opts.basePath || defaultDeployPath();
|
|
56
|
+
const { env, chain } = resolveDeploymentPath(network, opts.env);
|
|
57
|
+
const data = await readDeployFile(deployPath);
|
|
58
|
+
const addr = data[env]?.[chain]?.[key];
|
|
59
|
+
if (!addr) throw new Error(`no ${key} deployment in ${env}.${chain}`);
|
|
60
|
+
return addr;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Check if a contract address exists and is valid in deploy.json.
|
|
65
|
+
* @param network - hardhat network name
|
|
66
|
+
* @param key - contract key
|
|
67
|
+
* @param opts - optional basePath and env overrides
|
|
68
|
+
*/
|
|
69
|
+
export async function hasDeployment(network: string, key: string, opts: DeploymentOptions): Promise<boolean> {
|
|
70
|
+
try {
|
|
71
|
+
const deployPath = opts.basePath || defaultDeployPath();
|
|
72
|
+
const { env, chain } = resolveDeploymentPath(network, opts.env);
|
|
73
|
+
const data = await readDeployFile(deployPath);
|
|
74
|
+
const addr = data[env]?.[chain]?.[key];
|
|
75
|
+
return !!addr && addr.length > 2 && addr !== "0x";
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Save a deployed contract address to deploy.json.
|
|
83
|
+
* @param network - hardhat network name
|
|
84
|
+
* @param key - contract key (e.g. "Gateway")
|
|
85
|
+
* @param addr - deployed address
|
|
86
|
+
* @param opts - optional basePath and env overrides
|
|
87
|
+
*/
|
|
88
|
+
export async function saveDeployment(network: string, key: string, addr: string, opts: DeploymentOptions): Promise<void> {
|
|
89
|
+
const deployPath = opts.basePath || defaultDeployPath();
|
|
90
|
+
const { env, chain } = resolveDeploymentPath(network, opts.env);
|
|
91
|
+
const data = await readDeployFile(deployPath);
|
|
92
|
+
if (!data[env]) data[env] = {};
|
|
93
|
+
if (!data[env][chain]) data[env][chain] = {};
|
|
94
|
+
data[env][chain][key] = addr;
|
|
95
|
+
const filePath = path.resolve(deployPath, "deploy.json");
|
|
96
|
+
await ensureDir(deployPath);
|
|
97
|
+
await fs.promises.writeFile(filePath, JSON.stringify(data, null, "\t"));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function readDeployFile(basePath: string): Promise<DeployData> {
|
|
101
|
+
const filePath = path.resolve(basePath, "deploy.json");
|
|
102
|
+
try {
|
|
103
|
+
const rawdata = await fs.promises.readFile(filePath, "utf-8");
|
|
104
|
+
return JSON.parse(rawdata);
|
|
105
|
+
} catch {
|
|
106
|
+
return {};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function ensureDir(dirPath: string): Promise<void> {
|
|
111
|
+
const absPath = path.resolve(dirPath);
|
|
112
|
+
try {
|
|
113
|
+
await fs.promises.stat(absPath);
|
|
114
|
+
} catch {
|
|
115
|
+
await fs.promises.mkdir(absPath, { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { isTronNetwork, TronClient } from "./tronHelper";
|
|
2
|
+
import { evmDeploy, evmDeployProxy, evmUpgradeProxy } from "./evmHelper";
|
|
3
|
+
import { verify as _verify } from "./verifier";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Unified deployer — auto-routes to EVM or Tron based on network name.
|
|
7
|
+
*
|
|
8
|
+
* Usage in hardhat tasks:
|
|
9
|
+
* const deployer = createDeployer(hre, { autoVerify: true });
|
|
10
|
+
* let addr = await deployer.deploy("Gateway", [admin], salt);
|
|
11
|
+
* let { proxy, implementation } = await deployer.deployProxy("Gateway", [admin], salt);
|
|
12
|
+
* let newImpl = await deployer.upgrade("Gateway", proxyAddr);
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface DeployerOptions {
|
|
16
|
+
autoVerify?: boolean; // auto verify after deploy/upgrade, defaults to false
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface DeployResult {
|
|
20
|
+
address: string; // primary address (0x hex for EVM, base58 for Tron)
|
|
21
|
+
hex?: string; // 0x hex (Tron only, EVM same as address)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface DeployProxyResult {
|
|
25
|
+
proxy: string; // proxy address (0x hex for EVM, base58 for Tron)
|
|
26
|
+
implementation: string; // impl address
|
|
27
|
+
proxyHex?: string; // Tron hex
|
|
28
|
+
implementationHex?: string; // Tron hex
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface Deployer {
|
|
32
|
+
deploy(contractName: string, args?: any[], salt?: string): Promise<DeployResult>;
|
|
33
|
+
deployProxy(contractName: string, initArgs?: any[], salt?: string): Promise<DeployProxyResult>;
|
|
34
|
+
upgrade(contractName: string, proxyAddr: string): Promise<DeployResult>;
|
|
35
|
+
verify(contractName: string, address: string, constructorArgs?: any[], contractPath?: string): Promise<void>;
|
|
36
|
+
isTron: boolean;
|
|
37
|
+
network: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create a unified deployer that auto-routes to EVM or Tron based on network.
|
|
42
|
+
* @param hre - hardhat runtime environment
|
|
43
|
+
* @param opts - options (autoVerify: auto-verify after deploy, defaults to false)
|
|
44
|
+
*/
|
|
45
|
+
export function createDeployer(hre: any, opts: DeployerOptions = {}): Deployer {
|
|
46
|
+
const network = hre.network.name;
|
|
47
|
+
const isTron = isTronNetwork(network);
|
|
48
|
+
const autoVerify = opts.autoVerify || false;
|
|
49
|
+
|
|
50
|
+
async function tryVerify(contractName: string, address: string, constructorArgs?: any[], contractPath?: string) {
|
|
51
|
+
if (!autoVerify) return;
|
|
52
|
+
try {
|
|
53
|
+
await _verify(hre, { contractName, address, constructorArgs, contractPath });
|
|
54
|
+
} catch (e: any) {
|
|
55
|
+
console.log(`[warn] auto-verify failed: ${e.message || e}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (isTron) {
|
|
60
|
+
const client = TronClient.fromHre(hre);
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
isTron: true,
|
|
64
|
+
network,
|
|
65
|
+
|
|
66
|
+
async deploy(contractName, args = [], salt = "") {
|
|
67
|
+
let result = await client.deploy(hre.artifacts, contractName, args, salt);
|
|
68
|
+
await tryVerify(contractName, result.base58, args);
|
|
69
|
+
return { address: result.base58, hex: result.hex };
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
async deployProxy(contractName, initArgs = [], salt = "") {
|
|
73
|
+
let result = await client.deployProxy(hre.artifacts, contractName, initArgs, salt);
|
|
74
|
+
await tryVerify(contractName, result.implementation.base58);
|
|
75
|
+
return {
|
|
76
|
+
proxy: result.proxy.base58,
|
|
77
|
+
implementation: result.implementation.base58,
|
|
78
|
+
proxyHex: result.proxy.hex,
|
|
79
|
+
implementationHex: result.implementation.hex,
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
async upgrade(contractName, proxyAddr) {
|
|
84
|
+
let result = await client.upgradeProxy(hre.artifacts, contractName, proxyAddr);
|
|
85
|
+
await tryVerify(contractName, result.base58);
|
|
86
|
+
return { address: result.base58, hex: result.hex };
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
async verify(contractName, address, constructorArgs, contractPath) {
|
|
90
|
+
await _verify(hre, { contractName, address, constructorArgs, contractPath });
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// EVM
|
|
96
|
+
const { ethers, artifacts } = hre;
|
|
97
|
+
return {
|
|
98
|
+
isTron: false,
|
|
99
|
+
network,
|
|
100
|
+
|
|
101
|
+
async deploy(contractName, args = [], salt = "") {
|
|
102
|
+
let addr = await evmDeploy(ethers, artifacts, contractName, args, salt);
|
|
103
|
+
await tryVerify(contractName, addr, args);
|
|
104
|
+
return { address: addr };
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
async deployProxy(contractName, initArgs = [], salt = "") {
|
|
108
|
+
let result = await evmDeployProxy(ethers, artifacts, contractName, initArgs, salt);
|
|
109
|
+
await tryVerify(contractName, result.implementation);
|
|
110
|
+
return { proxy: result.proxy, implementation: result.implementation };
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
async upgrade(contractName, proxyAddr) {
|
|
114
|
+
let addr = await evmUpgradeProxy(ethers, contractName, proxyAddr);
|
|
115
|
+
await tryVerify(contractName, addr);
|
|
116
|
+
return { address: addr };
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
async verify(contractName, address, constructorArgs, contractPath) {
|
|
120
|
+
await _verify(hre, { contractName, address, constructorArgs, contractPath });
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deploy contract on EVM. If salt is provided, uses CREATE2 factory for deterministic address.
|
|
3
|
+
* @param ethers - ethers from hardhat runtime (hre.ethers)
|
|
4
|
+
* @param artifacts - hardhat artifacts (hre.artifacts)
|
|
5
|
+
* @param contractName - contract name to deploy
|
|
6
|
+
* @param args - constructor arguments array
|
|
7
|
+
* @param salt - optional CREATE2 salt for deterministic address
|
|
8
|
+
* @returns deployed contract address (0x hex)
|
|
9
|
+
*/
|
|
10
|
+
export async function evmDeploy(
|
|
11
|
+
ethers: any,
|
|
12
|
+
artifacts: any,
|
|
13
|
+
contractName: string,
|
|
14
|
+
args: any[] = [],
|
|
15
|
+
salt: string = ""
|
|
16
|
+
): Promise<string> {
|
|
17
|
+
if (salt) {
|
|
18
|
+
const { evmDeployByFactory } = require("./factory");
|
|
19
|
+
const artifact = await artifacts.readArtifact(contractName);
|
|
20
|
+
const constructorArgs = args.length > 0
|
|
21
|
+
? ethers.AbiCoder.defaultAbiCoder().encode(
|
|
22
|
+
artifact.abi
|
|
23
|
+
.find((x: any) => x.type === "constructor")
|
|
24
|
+
?.inputs.map((i: any) => i.type) || [],
|
|
25
|
+
args
|
|
26
|
+
)
|
|
27
|
+
: "0x";
|
|
28
|
+
return evmDeployByFactory(ethers, salt, artifact.bytecode, constructorArgs);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const [deployer] = await ethers.getSigners();
|
|
32
|
+
console.log("deploy address is:", deployer.address);
|
|
33
|
+
|
|
34
|
+
const ContractFactory = await ethers.getContractFactory(contractName);
|
|
35
|
+
const contract = await (await ContractFactory.deploy(...args)).waitForDeployment();
|
|
36
|
+
const addr = await contract.getAddress();
|
|
37
|
+
console.log(`${contractName} deployed: ${addr}`);
|
|
38
|
+
return addr;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Deploy implementation + ERC1967 proxy in one step.
|
|
43
|
+
* If salt is provided, proxy is deployed via factory.
|
|
44
|
+
* @param ethers - ethers from hardhat runtime (hre.ethers)
|
|
45
|
+
* @param artifacts - hardhat artifacts (hre.artifacts)
|
|
46
|
+
* @param contractName - implementation contract name
|
|
47
|
+
* @param initArgs - initialize() function arguments
|
|
48
|
+
* @param salt - optional CREATE2 salt for proxy
|
|
49
|
+
* @returns { proxy, implementation } addresses
|
|
50
|
+
*/
|
|
51
|
+
export async function evmDeployProxy(
|
|
52
|
+
ethers: any,
|
|
53
|
+
artifacts: any,
|
|
54
|
+
contractName: string,
|
|
55
|
+
initArgs: any[] = [],
|
|
56
|
+
salt: string = ""
|
|
57
|
+
): Promise<{ proxy: string; implementation: string }> {
|
|
58
|
+
const ImplFactory = await ethers.getContractFactory(contractName);
|
|
59
|
+
const impl = await (await ImplFactory.deploy()).waitForDeployment();
|
|
60
|
+
const implAddr = await impl.getAddress();
|
|
61
|
+
console.log(`${contractName} implementation: ${implAddr}`);
|
|
62
|
+
|
|
63
|
+
const initData = ImplFactory.interface.encodeFunctionData("initialize", initArgs);
|
|
64
|
+
|
|
65
|
+
let proxyAddr: string;
|
|
66
|
+
if (salt) {
|
|
67
|
+
const { evmDeployByFactory } = require("./factory");
|
|
68
|
+
const proxyArtifact = await artifacts.readArtifact("ERC1967Proxy");
|
|
69
|
+
const constructorArgs = ethers.AbiCoder.defaultAbiCoder().encode(
|
|
70
|
+
["address", "bytes"],
|
|
71
|
+
[implAddr, initData]
|
|
72
|
+
);
|
|
73
|
+
proxyAddr = await evmDeployByFactory(ethers, salt, proxyArtifact.bytecode, constructorArgs);
|
|
74
|
+
} else {
|
|
75
|
+
const ProxyFactory = await ethers.getContractFactory("ERC1967Proxy");
|
|
76
|
+
const proxy = await (await ProxyFactory.deploy(implAddr, initData)).waitForDeployment();
|
|
77
|
+
proxyAddr = await proxy.getAddress();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log(`${contractName} proxy: ${proxyAddr}`);
|
|
81
|
+
return { proxy: proxyAddr, implementation: implAddr };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Upgrade a UUPS proxy to a new implementation.
|
|
86
|
+
* Deploys new implementation, then calls upgradeToAndCall on the proxy.
|
|
87
|
+
* @param ethers - ethers from hardhat runtime (hre.ethers)
|
|
88
|
+
* @param contractName - new implementation contract name
|
|
89
|
+
* @param proxyAddr - proxy address to upgrade
|
|
90
|
+
* @returns new implementation address
|
|
91
|
+
*/
|
|
92
|
+
export async function evmUpgradeProxy(
|
|
93
|
+
ethers: any,
|
|
94
|
+
contractName: string,
|
|
95
|
+
proxyAddr: string
|
|
96
|
+
): Promise<string> {
|
|
97
|
+
const [deployer] = await ethers.getSigners();
|
|
98
|
+
const ImplFactory = await ethers.getContractFactory(contractName);
|
|
99
|
+
const impl = await (await ImplFactory.deploy()).waitForDeployment();
|
|
100
|
+
const implAddr = await impl.getAddress();
|
|
101
|
+
|
|
102
|
+
const proxy = await ethers.getContractAt("BaseImplementation", proxyAddr, deployer);
|
|
103
|
+
const oldImpl = await proxy.getImplementation();
|
|
104
|
+
await (await proxy.upgradeToAndCall(implAddr, "0x")).wait();
|
|
105
|
+
|
|
106
|
+
console.log(`${contractName} upgraded: ${oldImpl} -> ${implAddr}`);
|
|
107
|
+
return implAddr;
|
|
108
|
+
}
|
package/utils/factory.ts
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory deployment — deterministic contract addresses across chains.
|
|
3
|
+
* EVM: CREATE3 factory at 0x6258e4d2950757A749a4d4683A7342261ce12471
|
|
4
|
+
* Tron: CREATE2 factory at TJAwD5VfMYMGWwdPi2aVcoQwVBfsuw5wQt
|
|
5
|
+
*/
|
|
6
|
+
import { tronFromHex } from "./tronHelper";
|
|
7
|
+
|
|
8
|
+
// Factory contract addresses
|
|
9
|
+
const EVM_FACTORY = "0x6258e4d2950757A749a4d4683A7342261ce12471";
|
|
10
|
+
const TRON_FACTORY = "TJAwD5VfMYMGWwdPi2aVcoQwVBfsuw5wQt";
|
|
11
|
+
|
|
12
|
+
// EVM factory ABI (CREATE3 — getAddress only needs salt)
|
|
13
|
+
// function deploy(bytes32 salt, bytes creationCode, uint256 value)
|
|
14
|
+
// function getAddress(bytes32 salt) view returns (address)
|
|
15
|
+
const EVM_FACTORY_ABI = [
|
|
16
|
+
{
|
|
17
|
+
"inputs": [{"name": "salt", "type": "bytes32"}, {"name": "creationCode", "type": "bytes"}, {"name": "value", "type": "uint256"}],
|
|
18
|
+
"name": "deploy",
|
|
19
|
+
"outputs": [],
|
|
20
|
+
"stateMutability": "nonpayable",
|
|
21
|
+
"type": "function"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"inputs": [{"name": "salt", "type": "bytes32"}],
|
|
25
|
+
"name": "getAddress",
|
|
26
|
+
"outputs": [{"name": "", "type": "address"}],
|
|
27
|
+
"stateMutability": "view",
|
|
28
|
+
"type": "function"
|
|
29
|
+
}
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
// Tron factory ABI (CREATE2 — getAddress needs salt + codeHash)
|
|
33
|
+
// function deploy(bytes32 salt, bytes creationCode, uint256 value) returns (address)
|
|
34
|
+
// function getAddress(bytes32 salt, bytes32 codeHash) view returns (address)
|
|
35
|
+
// function getAddressTron(bytes32 salt, bytes32 codeHash) view returns (address)
|
|
36
|
+
const TRON_FACTORY_ABI = [
|
|
37
|
+
{
|
|
38
|
+
"inputs": [{"name": "salt", "type": "bytes32"}, {"name": "creationCode", "type": "bytes"}, {"name": "value", "type": "uint256"}],
|
|
39
|
+
"name": "deploy",
|
|
40
|
+
"outputs": [{"name": "addr", "type": "address"}],
|
|
41
|
+
"stateMutability": "nonpayable",
|
|
42
|
+
"type": "function"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"inputs": [{"name": "salt", "type": "bytes32"}, {"name": "codeHash", "type": "bytes32"}],
|
|
46
|
+
"name": "getAddressTron",
|
|
47
|
+
"outputs": [{"name": "", "type": "address"}],
|
|
48
|
+
"stateMutability": "view",
|
|
49
|
+
"type": "function"
|
|
50
|
+
}
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
// ============================================================
|
|
54
|
+
// EVM Factory (CREATE3, ethers.js)
|
|
55
|
+
// ============================================================
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Deploy contract via CREATE3 factory on EVM chains.
|
|
59
|
+
* Address only depends on salt, not bytecode.
|
|
60
|
+
* @param ethers - ethers from hardhat runtime (hre.ethers)
|
|
61
|
+
* @param salt - human-readable salt string
|
|
62
|
+
* @param bytecode - contract bytecode (artifact.bytecode)
|
|
63
|
+
* @param constructorArgs - ABI-encoded constructor arguments
|
|
64
|
+
* @returns deployed contract address
|
|
65
|
+
*/
|
|
66
|
+
export async function evmDeployByFactory(
|
|
67
|
+
ethers: any,
|
|
68
|
+
salt: string,
|
|
69
|
+
bytecode: string,
|
|
70
|
+
constructorArgs: string = "0x"
|
|
71
|
+
): Promise<string> {
|
|
72
|
+
const [signer] = await ethers.getSigners();
|
|
73
|
+
const factory = new ethers.Contract(EVM_FACTORY, EVM_FACTORY_ABI, signer);
|
|
74
|
+
|
|
75
|
+
const code = await ethers.provider.getCode(EVM_FACTORY);
|
|
76
|
+
if (code === "0x") throw new Error("factory not deployed on this chain");
|
|
77
|
+
|
|
78
|
+
const saltHash = ethers.keccak256(ethers.toUtf8Bytes(salt));
|
|
79
|
+
const predicted = await factory.getAddress(saltHash);
|
|
80
|
+
|
|
81
|
+
const existingCode = await ethers.provider.getCode(predicted);
|
|
82
|
+
if (existingCode !== "0x") {
|
|
83
|
+
console.log(`already deployed at ${predicted}`);
|
|
84
|
+
return predicted;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const fullBytecode = constructorArgs === "0x"
|
|
88
|
+
? bytecode
|
|
89
|
+
: ethers.concat([bytecode, constructorArgs]);
|
|
90
|
+
|
|
91
|
+
const tx = await factory.deploy(saltHash, fullBytecode, 0);
|
|
92
|
+
await tx.wait();
|
|
93
|
+
|
|
94
|
+
// Verify contract was actually deployed
|
|
95
|
+
const deployedCode = await ethers.provider.getCode(predicted);
|
|
96
|
+
if (deployedCode === "0x") {
|
|
97
|
+
throw new Error(`factory deploy failed: no contract at predicted address ${predicted}`);
|
|
98
|
+
}
|
|
99
|
+
console.log(`deployed via factory at ${predicted}`);
|
|
100
|
+
return predicted;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get predicted factory address for a salt on EVM (CREATE3 — only needs salt).
|
|
105
|
+
* @param ethers - ethers from hardhat runtime
|
|
106
|
+
* @param salt - human-readable salt string
|
|
107
|
+
*/
|
|
108
|
+
export async function evmGetFactoryAddress(ethers: any, salt: string): Promise<string> {
|
|
109
|
+
const factory = new ethers.Contract(EVM_FACTORY, EVM_FACTORY_ABI, await ethers.provider);
|
|
110
|
+
const saltHash = ethers.keccak256(ethers.toUtf8Bytes(salt));
|
|
111
|
+
return factory.getAddress(saltHash);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ============================================================
|
|
115
|
+
// Tron Factory (CREATE2, tronweb)
|
|
116
|
+
// ============================================================
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Deploy contract via CREATE2 factory on Tron.
|
|
120
|
+
* Address depends on salt + creationCode (bytecode + constructor args).
|
|
121
|
+
* @param tronWeb - initialized tronweb instance
|
|
122
|
+
* @param artifacts - hardhat artifacts
|
|
123
|
+
* @param contractName - contract name to deploy
|
|
124
|
+
* @param salt - human-readable salt string
|
|
125
|
+
* @param args - constructor arguments array
|
|
126
|
+
* @param feeLimit - tron fee limit
|
|
127
|
+
* @returns deployed contract address (0x-prefixed hex)
|
|
128
|
+
*/
|
|
129
|
+
export async function tronDeployByFactory(
|
|
130
|
+
tronWeb: any,
|
|
131
|
+
artifacts: any,
|
|
132
|
+
contractName: string,
|
|
133
|
+
salt: string,
|
|
134
|
+
args: any[] = [],
|
|
135
|
+
feeLimit: number = 15_000_000_000
|
|
136
|
+
): Promise<string> {
|
|
137
|
+
const factory = await tronWeb.contract(TRON_FACTORY_ABI, TRON_FACTORY);
|
|
138
|
+
const saltHash = tronWeb.sha3(salt);
|
|
139
|
+
|
|
140
|
+
// Build creation code with constructor args
|
|
141
|
+
const artifact = await artifacts.readArtifact(contractName);
|
|
142
|
+
let creationCode = artifact.bytecode;
|
|
143
|
+
if (args.length > 0) {
|
|
144
|
+
const iface = new (require("ethers").Interface)(artifact.abi);
|
|
145
|
+
const encoded = iface.encodeDeploy(args);
|
|
146
|
+
creationCode = creationCode + encoded.slice(2);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Check if already deployed
|
|
150
|
+
const codeHash = tronWeb.sha3(creationCode);
|
|
151
|
+
const predicted = await factory.getAddressTron(saltHash, codeHash).call();
|
|
152
|
+
const predictedHex = predicted.replace(/^41/, "0x");
|
|
153
|
+
try {
|
|
154
|
+
const existing = await tronWeb.trx.getContract(tronWeb.address.fromHex(predicted));
|
|
155
|
+
if (existing && existing.bytecode) {
|
|
156
|
+
console.log(`already deployed at ${tronFromHex(predicted)}`);
|
|
157
|
+
return predictedHex;
|
|
158
|
+
}
|
|
159
|
+
} catch {}
|
|
160
|
+
|
|
161
|
+
// Deploy
|
|
162
|
+
console.log(`deploying ${contractName} via factory with salt "${salt}"...`);
|
|
163
|
+
const { sendAndWait } = require("./tronHelper");
|
|
164
|
+
const txResult = await sendAndWait(factory.deploy(saltHash, creationCode, 0), tronWeb, { feeLimit });
|
|
165
|
+
|
|
166
|
+
// Read actual address from Deployed event
|
|
167
|
+
let addrHex = predictedHex; // fallback to predicted
|
|
168
|
+
if (txResult.log && txResult.log.length > 0) {
|
|
169
|
+
// Deployed(address indexed addr, bytes32 indexed salt)
|
|
170
|
+
// First topic = event sig, second topic = addr
|
|
171
|
+
const addrTopic = txResult.log[0].topics?.[1];
|
|
172
|
+
if (addrTopic) {
|
|
173
|
+
addrHex = "0x" + addrTopic.slice(24); // last 20 bytes
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Verify contract was actually deployed
|
|
178
|
+
const addrBase58 = tronFromHex(addrHex);
|
|
179
|
+
try {
|
|
180
|
+
const deployed = await tronWeb.trx.getContract(addrBase58);
|
|
181
|
+
if (!deployed || !deployed.bytecode) {
|
|
182
|
+
throw new Error(`factory deploy failed: no contract at ${addrBase58}`);
|
|
183
|
+
}
|
|
184
|
+
} catch (e: any) {
|
|
185
|
+
if (e.message?.includes("factory deploy failed")) throw e;
|
|
186
|
+
// Contract might not be indexed yet, wait and retry
|
|
187
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
188
|
+
const deployed = await tronWeb.trx.getContract(addrBase58);
|
|
189
|
+
if (!deployed || !deployed.bytecode) {
|
|
190
|
+
throw new Error(`factory deploy failed: no contract at ${addrBase58}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
console.log(`${contractName} deployed: ${addrBase58} (${addrHex})`);
|
|
195
|
+
return addrHex;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Get predicted factory address for a salt on Tron (CREATE2 — needs salt + codeHash).
|
|
200
|
+
* @param tronWeb - initialized tronweb instance
|
|
201
|
+
* @param salt - human-readable salt string
|
|
202
|
+
* @param codeHash - keccak256 of creationCode (bytecode + constructor args)
|
|
203
|
+
* @returns address in base58 format
|
|
204
|
+
*/
|
|
205
|
+
export async function tronGetFactoryAddress(tronWeb: any, salt: string, codeHash: string): Promise<string> {
|
|
206
|
+
const factory = await tronWeb.contract(TRON_FACTORY_ABI, TRON_FACTORY);
|
|
207
|
+
const saltHash = tronWeb.sha3(salt);
|
|
208
|
+
const addr = await factory.getAddressTron(saltHash, codeHash).call();
|
|
209
|
+
return tronFromHex(addr);
|
|
210
|
+
}
|