create-stylus 1.1.0 → 1.1.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/package.json +1 -1
- package/templates/base/.gitignore.template.mjs +2 -2
- package/templates/base/dist/cli.js +683 -0
- package/templates/base/dist/cli.js.map +1 -0
- package/templates/base/package.json +1 -0
- package/templates/base/packages/nextjs/.gitignore.template.mjs +3 -3
- package/templates/base/packages/nextjs/scaffold.config.ts +2 -2
- package/templates/base/packages/stylus/.env.example +5 -2
- package/templates/base/packages/stylus/.gitignore.template.mjs +3 -3
- package/templates/base/packages/stylus/package.json +0 -1
- package/templates/base/packages/stylus/scripts/deploy.ts +29 -12
- package/templates/base/packages/stylus/scripts/deploy_contract.ts +34 -43
- package/templates/base/packages/stylus/scripts/deploy_wrapper.ts +4 -44
- package/templates/base/packages/stylus/scripts/export_abi.ts +13 -13
- package/templates/base/packages/stylus/scripts/utils/command.ts +18 -31
- package/templates/base/packages/stylus/scripts/utils/contract.ts +23 -14
- package/templates/base/packages/stylus/scripts/utils/deployment.ts +169 -45
- package/templates/base/packages/stylus/scripts/utils/network.ts +27 -7
- package/templates/base/packages/stylus/scripts/utils/type.ts +13 -10
- package/templates/base/packages/stylus/your-contract/Cargo.lock +35 -18
- package/templates/base/packages/stylus/your-contract/Cargo.toml +3 -1
- package/templates/base/packages/stylus/your-contract/src/lib.rs +51 -21
- package/templates/base/readme.md +208 -43
- package/templates/base/yarn.lock +1 -2
- package/templates/base/packages/stylus/README.md +0 -263
- package/templates/base/packages/stylus/header.png +0 -0
- package/templates/base/packages/stylus/scripts/deploy_all_contracts.ts +0 -59
|
@@ -2,8 +2,8 @@ import { config as dotenvConfig } from "dotenv";
|
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import * as fs from "fs";
|
|
4
4
|
import { arbitrumNitro } from "../../../nextjs/utils/scaffold-stylus/chain";
|
|
5
|
-
import { DeploymentConfig, DeployOptions } from "./type";
|
|
6
|
-
import { getChain, getPrivateKey } from "./network";
|
|
5
|
+
import { DeploymentConfig, DeployOptions, DeploymentData } from "./type";
|
|
6
|
+
import { getAccountAddress, getChain, getPrivateKey } from "./network";
|
|
7
7
|
import { getContractNameFromCargoToml } from "./contract";
|
|
8
8
|
|
|
9
9
|
// Load environment variables from .env file
|
|
@@ -42,6 +42,7 @@ export function getDeploymentConfig(
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
return {
|
|
45
|
+
deployerAddress: getAccountAddress(deployOptions.network),
|
|
45
46
|
privateKey: getPrivateKey(deployOptions.network),
|
|
46
47
|
contractFolder: deployOptions.contract!,
|
|
47
48
|
contractName,
|
|
@@ -58,79 +59,202 @@ export function ensureDeploymentDirectory(deploymentDir: string): void {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
/**
|
|
61
|
-
* Save the deployed contract address to
|
|
62
|
+
* Save the deployed contract address to <chain.id>_latest.json in the deployment directory.
|
|
63
|
+
* If a latest file already exists, it gets renamed to include a timestamp.
|
|
62
64
|
* Updates or creates the file, using contractName as the key.
|
|
63
65
|
*/
|
|
64
|
-
export function
|
|
66
|
+
export function saveDeployment(
|
|
67
|
+
config: DeploymentConfig,
|
|
68
|
+
deploymentInfo: DeploymentData,
|
|
69
|
+
) {
|
|
65
70
|
try {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
const chainId = config.chain?.id || arbitrumNitro.id;
|
|
72
|
+
const networkPath = path.resolve(
|
|
73
|
+
config.deploymentDir,
|
|
74
|
+
`${chainId}_latest.json`,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// Check if the latest file exists and contains the same contract name
|
|
78
|
+
let shouldCreateNewFile = false;
|
|
79
|
+
if (fs.existsSync(networkPath)) {
|
|
80
|
+
try {
|
|
81
|
+
const existingDeployments = JSON.parse(
|
|
82
|
+
fs.readFileSync(networkPath, "utf8"),
|
|
83
|
+
);
|
|
84
|
+
if (existingDeployments[config.contractName]) {
|
|
85
|
+
// Contract with same name already exists, create new file
|
|
86
|
+
shouldCreateNewFile = true;
|
|
87
|
+
}
|
|
88
|
+
} catch (e) {
|
|
89
|
+
console.warn(
|
|
90
|
+
`⚠️ Could not parse existing ${chainId}_latest.json, will overwrite. Error: ${e}`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// If we need to create a new file (contract name already exists), backup the current latest file
|
|
96
|
+
if (shouldCreateNewFile) {
|
|
97
|
+
const currentTimestamp = new Date().getTime();
|
|
98
|
+
const backupPath = networkPath.replace(
|
|
99
|
+
"_latest.json",
|
|
100
|
+
`_${currentTimestamp}.json`,
|
|
101
|
+
);
|
|
102
|
+
fs.renameSync(networkPath, backupPath);
|
|
103
|
+
console.log(`📦 Backed up previous deployment to ${backupPath}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Read existing deployments or start fresh
|
|
107
|
+
let deployments: Record<string, any> = {};
|
|
108
|
+
if (fs.existsSync(networkPath)) {
|
|
109
|
+
const content = fs.readFileSync(networkPath, "utf8");
|
|
70
110
|
try {
|
|
71
|
-
|
|
111
|
+
deployments = JSON.parse(content);
|
|
72
112
|
} catch (e) {
|
|
73
113
|
console.warn(
|
|
74
|
-
`⚠️ Could not parse existing
|
|
114
|
+
`⚠️ Could not parse existing ${chainId}_latest.json, will overwrite. Error: ${e}`,
|
|
75
115
|
);
|
|
76
116
|
}
|
|
77
117
|
}
|
|
78
118
|
|
|
79
|
-
// Save
|
|
80
|
-
|
|
81
|
-
address:
|
|
82
|
-
|
|
119
|
+
// Save with the new format
|
|
120
|
+
deployments[config.contractName] = {
|
|
121
|
+
address: deploymentInfo.address,
|
|
122
|
+
txHash: deploymentInfo.txHash,
|
|
123
|
+
contract: config.contractFolder,
|
|
83
124
|
};
|
|
84
125
|
|
|
85
|
-
fs.writeFileSync(
|
|
86
|
-
console.log(`💾 Saved deployed
|
|
126
|
+
fs.writeFileSync(networkPath, JSON.stringify(deployments, null, 2));
|
|
127
|
+
console.log(`💾 Saved deployed contract to ${networkPath}`);
|
|
87
128
|
} catch (e) {
|
|
88
|
-
console.error(`❌ Failed to save deployed
|
|
129
|
+
console.error(`❌ Failed to save deployed contract: ${e}`);
|
|
89
130
|
}
|
|
90
131
|
}
|
|
91
132
|
|
|
92
|
-
export function printDeployedAddresses(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
133
|
+
export function printDeployedAddresses(
|
|
134
|
+
deploymentDir: string,
|
|
135
|
+
chainId?: string,
|
|
136
|
+
): void {
|
|
137
|
+
// If chainId is provided, only look for that specific chain's deployment file
|
|
138
|
+
if (chainId) {
|
|
139
|
+
const networkPath = path.resolve(deploymentDir, `${chainId}_latest.json`);
|
|
140
|
+
if (!fs.existsSync(networkPath)) {
|
|
141
|
+
console.log(
|
|
142
|
+
`📦 No deployment file found for chain ${chainId} in ${deploymentDir}`,
|
|
143
|
+
);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const deployments = JSON.parse(fs.readFileSync(networkPath, "utf8"));
|
|
149
|
+
console.log(
|
|
150
|
+
`📦 Deployed contracts for chain ${chainId} (${networkPath}):`,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// Format the output to show contract name, address, and contract folder clearly
|
|
154
|
+
Object.entries(deployments).forEach(([contractName, contractData]) => {
|
|
155
|
+
const data = contractData as {
|
|
156
|
+
address: string;
|
|
157
|
+
txHash: string;
|
|
158
|
+
contract: string;
|
|
159
|
+
};
|
|
160
|
+
console.log(` ${contractName}:`);
|
|
161
|
+
console.log(` Address: ${data.address}`);
|
|
162
|
+
console.log(` Tx Hash: ${data.txHash}`);
|
|
163
|
+
console.log(` Contract: ${data.contract}`);
|
|
164
|
+
});
|
|
165
|
+
} catch (e) {
|
|
166
|
+
console.warn(`⚠️ Could not parse deployment file ${networkPath}: ${e}`);
|
|
167
|
+
}
|
|
168
|
+
return;
|
|
108
169
|
}
|
|
170
|
+
|
|
171
|
+
// If no chainId provided, look for all chain-specific deployment files
|
|
172
|
+
const files = fs.readdirSync(deploymentDir);
|
|
173
|
+
const deploymentFiles = files.filter((file) => file.endsWith("_latest.json"));
|
|
174
|
+
|
|
175
|
+
if (deploymentFiles.length === 0) {
|
|
176
|
+
console.log(`📦 No deployment files found in ${deploymentDir}`);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
deploymentFiles.forEach((file) => {
|
|
181
|
+
const filePath = path.resolve(deploymentDir, file);
|
|
182
|
+
const currentChainId = file.replace("_latest.json", "");
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const deployments = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
186
|
+
console.log(
|
|
187
|
+
`📦 Deployed contracts for chain ${currentChainId} (${filePath}):`,
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// Format the output to show contract name, address, and contract folder clearly
|
|
191
|
+
Object.entries(deployments).forEach(([contractName, contractData]) => {
|
|
192
|
+
const data = contractData as {
|
|
193
|
+
address: string;
|
|
194
|
+
txHash: string;
|
|
195
|
+
contract: string;
|
|
196
|
+
};
|
|
197
|
+
console.log(` ${contractName}:`);
|
|
198
|
+
console.log(` Address: ${data.address}`);
|
|
199
|
+
console.log(` Contract: ${data.contract}`);
|
|
200
|
+
});
|
|
201
|
+
} catch (e) {
|
|
202
|
+
console.warn(`⚠️ Could not parse deployment file ${filePath}: ${e}`);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
109
205
|
}
|
|
110
206
|
|
|
111
207
|
/**
|
|
112
|
-
* Reads the deployed contract data from
|
|
208
|
+
* Reads the deployed contract data from chain-specific deployment files in the deployment directory.
|
|
113
209
|
* Returns an object with address and chainId for the given contractName, or undefined if not found.
|
|
114
210
|
*/
|
|
115
211
|
export function getContractDataFromDeployments(
|
|
116
212
|
deploymentDir: string,
|
|
117
213
|
contractName: string,
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
214
|
+
chainId?: string,
|
|
215
|
+
): { address: string; txHash: string; chainId: string } | undefined {
|
|
216
|
+
// If chainId is provided, look for that specific chain's deployment file
|
|
217
|
+
if (chainId) {
|
|
218
|
+
const networkPath = path.resolve(deploymentDir, `${chainId}_latest.json`);
|
|
219
|
+
if (fs.existsSync(networkPath)) {
|
|
220
|
+
try {
|
|
221
|
+
const deployments = JSON.parse(fs.readFileSync(networkPath, "utf8"));
|
|
222
|
+
if (deployments[contractName]?.address) {
|
|
223
|
+
return {
|
|
224
|
+
address: deployments[contractName].address,
|
|
225
|
+
txHash: deployments[contractName].txHash,
|
|
226
|
+
chainId: chainId,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
} catch (e) {
|
|
230
|
+
console.warn(
|
|
231
|
+
`⚠️ Could not parse deployment file at ${networkPath}: ${e}`,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// If no chainId provided, search all deployment files
|
|
239
|
+
const files = fs.readdirSync(deploymentDir);
|
|
240
|
+
const deploymentFiles = files.filter((file) => file.endsWith("_latest.json"));
|
|
241
|
+
|
|
242
|
+
for (const file of deploymentFiles) {
|
|
243
|
+
const filePath = path.resolve(deploymentDir, file);
|
|
244
|
+
const currentChainId = file.replace("_latest.json", "");
|
|
121
245
|
try {
|
|
122
|
-
const
|
|
123
|
-
if (
|
|
124
|
-
return
|
|
125
|
-
address:
|
|
126
|
-
|
|
246
|
+
const deployments = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
247
|
+
if (deployments[contractName]?.address) {
|
|
248
|
+
return {
|
|
249
|
+
address: deployments[contractName].address,
|
|
250
|
+
txHash: deployments[contractName].txHash,
|
|
251
|
+
chainId: currentChainId,
|
|
127
252
|
};
|
|
128
253
|
}
|
|
129
254
|
} catch (e) {
|
|
130
|
-
console.warn(
|
|
131
|
-
`⚠️ Could not parse addresses.json at ${addressesPath}: ${e}`,
|
|
132
|
-
);
|
|
255
|
+
console.warn(`⚠️ Could not parse deployment file at ${filePath}: ${e}`);
|
|
133
256
|
}
|
|
134
257
|
}
|
|
258
|
+
|
|
135
259
|
return undefined;
|
|
136
260
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { arbitrum, arbitrumSepolia } from "viem/chains";
|
|
2
|
-
import { Chain } from "viem";
|
|
2
|
+
import { Address, Chain } from "viem";
|
|
3
3
|
import { arbitrumNitro } from "../../../nextjs/utils/scaffold-stylus/chain";
|
|
4
4
|
import * as path from "path";
|
|
5
5
|
import * as fs from "fs";
|
|
@@ -25,10 +25,8 @@ export const ALIASES: Record<string, string> = {
|
|
|
25
25
|
|
|
26
26
|
export function getChain(networkName: string): SupportedNetworkMinimal | null {
|
|
27
27
|
try {
|
|
28
|
-
// Normalize network name to lowercase for case-insensitive lookup
|
|
29
28
|
const actualNetworkName = ALIASES[networkName.toLowerCase()] || networkName;
|
|
30
29
|
|
|
31
|
-
// Find the chain in SUPPORTED_NETWORKS (case-insensitive)
|
|
32
30
|
const chainEntry = Object.entries(SUPPORTED_NETWORKS).find(
|
|
33
31
|
([key]) => key.toLowerCase() === actualNetworkName.toLowerCase(),
|
|
34
32
|
);
|
|
@@ -42,7 +40,6 @@ export function getChain(networkName: string): SupportedNetworkMinimal | null {
|
|
|
42
40
|
};
|
|
43
41
|
}
|
|
44
42
|
|
|
45
|
-
// If not found, show warning with all supported networks
|
|
46
43
|
const supportedNetworks = Object.keys(SUPPORTED_NETWORKS);
|
|
47
44
|
console.warn(
|
|
48
45
|
`⚠️ Network '${networkName}' is not supported. Supported networks: ${supportedNetworks.join(", ")}`,
|
|
@@ -59,9 +56,17 @@ export function getPrivateKey(networkName: string): string {
|
|
|
59
56
|
|
|
60
57
|
switch (actualNetworkName.toLowerCase()) {
|
|
61
58
|
case "arbitrum":
|
|
62
|
-
|
|
59
|
+
if (process.env["PRIVATE_KEY_MAINNET"]) {
|
|
60
|
+
return process.env["PRIVATE_KEY_MAINNET"];
|
|
61
|
+
} else {
|
|
62
|
+
throw new Error("PRIVATE_KEY_MAINNET is not set");
|
|
63
|
+
}
|
|
63
64
|
case "arbitrumsepolia":
|
|
64
|
-
|
|
65
|
+
if (process.env["PRIVATE_KEY_SEPOLIA"]) {
|
|
66
|
+
return process.env["PRIVATE_KEY_SEPOLIA"];
|
|
67
|
+
} else {
|
|
68
|
+
throw new Error("PRIVATE_KEY_SEPOLIA is not set");
|
|
69
|
+
}
|
|
65
70
|
default:
|
|
66
71
|
return (
|
|
67
72
|
process.env["PRIVATE_KEY"] ||
|
|
@@ -70,6 +75,21 @@ export function getPrivateKey(networkName: string): string {
|
|
|
70
75
|
}
|
|
71
76
|
}
|
|
72
77
|
|
|
78
|
+
export const getAccountAddress = (networkName: string): Address | undefined => {
|
|
79
|
+
const actualNetworkName = ALIASES[networkName.toLowerCase()] || networkName;
|
|
80
|
+
switch (actualNetworkName.toLowerCase()) {
|
|
81
|
+
case "arbitrum":
|
|
82
|
+
return process.env["ACCOUNT_ADDRESS_MAINNET"] as Address;
|
|
83
|
+
case "arbitrumsepolia":
|
|
84
|
+
return process.env["ACCOUNT_ADDRESS_SEPOLIA"] as Address;
|
|
85
|
+
default:
|
|
86
|
+
return (
|
|
87
|
+
(process.env["ACCOUNT_ADDRESS"] as Address) ||
|
|
88
|
+
"0x3f1Eae7D46d88F08fc2F8ed27FCb2AB183EB2d0E"
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
73
93
|
function getRpcUrlFromChain(chain: Chain): string {
|
|
74
94
|
//Prefer user rpc url from env
|
|
75
95
|
switch (chain.id) {
|
|
@@ -109,4 +129,4 @@ function getAliasFromNetworkName(networkName: string): string {
|
|
|
109
129
|
Object.entries(ALIASES).find(([, alias]) => alias === networkName)?.[0] ||
|
|
110
130
|
networkName
|
|
111
131
|
);
|
|
112
|
-
}
|
|
132
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Address } from "viem";
|
|
2
|
+
|
|
1
3
|
interface BaseCommandOptions {
|
|
2
4
|
_: (string | number)[];
|
|
3
5
|
$0: string;
|
|
@@ -6,29 +8,29 @@ interface BaseCommandOptions {
|
|
|
6
8
|
|
|
7
9
|
export interface DeployCommandOptions
|
|
8
10
|
extends BaseCommandOptions,
|
|
9
|
-
DeployOptions {
|
|
10
|
-
all?: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface AdditionalOptions {
|
|
14
|
-
isSingleCommand?: boolean;
|
|
15
|
-
shouldClearDeploymentDir?: boolean;
|
|
16
|
-
}
|
|
11
|
+
DeployOptions {}
|
|
17
12
|
|
|
18
13
|
export interface DeployOptions {
|
|
19
14
|
contract?: string;
|
|
20
15
|
name?: string;
|
|
16
|
+
constructorArgs?: string[];
|
|
21
17
|
network?: string;
|
|
22
18
|
estimateGas?: boolean;
|
|
23
19
|
maxFee?: string;
|
|
20
|
+
verify?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DeploymentData {
|
|
24
|
+
address: Address;
|
|
25
|
+
txHash: string;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
export interface DeploymentConfig {
|
|
29
|
+
deployerAddress: Address | undefined;
|
|
27
30
|
privateKey: string;
|
|
28
31
|
contractFolder: string;
|
|
29
32
|
contractName: string;
|
|
30
33
|
deploymentDir: string;
|
|
31
|
-
contractAddress?: string;
|
|
32
34
|
chain?: SupportedNetworkMinimal;
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -36,7 +38,8 @@ export interface ExportConfig {
|
|
|
36
38
|
contractFolder: string;
|
|
37
39
|
contractName: string;
|
|
38
40
|
deploymentDir: string;
|
|
39
|
-
contractAddress:
|
|
41
|
+
contractAddress: Address;
|
|
42
|
+
txHash: string;
|
|
40
43
|
chainId: string;
|
|
41
44
|
}
|
|
42
45
|
|
|
@@ -382,9 +382,9 @@ dependencies = [
|
|
|
382
382
|
|
|
383
383
|
[[package]]
|
|
384
384
|
name = "alloy-sol-macro"
|
|
385
|
-
version = "0.8.
|
|
385
|
+
version = "0.8.20"
|
|
386
386
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
387
|
-
checksum = "
|
|
387
|
+
checksum = "13f28f2131dc3a7b8e2cda882758ad4d5231ca26281b9861d4b18c700713e2da"
|
|
388
388
|
dependencies = [
|
|
389
389
|
"alloy-sol-macro-expander",
|
|
390
390
|
"alloy-sol-macro-input",
|
|
@@ -396,9 +396,9 @@ dependencies = [
|
|
|
396
396
|
|
|
397
397
|
[[package]]
|
|
398
398
|
name = "alloy-sol-macro-expander"
|
|
399
|
-
version = "0.8.
|
|
399
|
+
version = "0.8.20"
|
|
400
400
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
401
|
-
checksum = "
|
|
401
|
+
checksum = "1ee2da033256a3b27131c030933eab0460a709fbcc4d4bd57bf9a5650b2441c5"
|
|
402
402
|
dependencies = [
|
|
403
403
|
"alloy-sol-macro-input",
|
|
404
404
|
"const-hex",
|
|
@@ -414,14 +414,13 @@ dependencies = [
|
|
|
414
414
|
|
|
415
415
|
[[package]]
|
|
416
416
|
name = "alloy-sol-macro-input"
|
|
417
|
-
version = "0.8.
|
|
417
|
+
version = "0.8.20"
|
|
418
418
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
419
|
-
checksum = "
|
|
419
|
+
checksum = "4e9d9918b0abb632818bf27e2dfb86b209be8433baacf22100b190bbc0904bd4"
|
|
420
420
|
dependencies = [
|
|
421
421
|
"const-hex",
|
|
422
422
|
"dunce",
|
|
423
423
|
"heck",
|
|
424
|
-
"macro-string",
|
|
425
424
|
"proc-macro2",
|
|
426
425
|
"quote",
|
|
427
426
|
"syn 2.0.101",
|
|
@@ -2824,17 +2823,6 @@ dependencies = [
|
|
|
2824
2823
|
"hashbrown 0.15.3",
|
|
2825
2824
|
]
|
|
2826
2825
|
|
|
2827
|
-
[[package]]
|
|
2828
|
-
name = "macro-string"
|
|
2829
|
-
version = "0.1.4"
|
|
2830
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2831
|
-
checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3"
|
|
2832
|
-
dependencies = [
|
|
2833
|
-
"proc-macro2",
|
|
2834
|
-
"quote",
|
|
2835
|
-
"syn 2.0.101",
|
|
2836
|
-
]
|
|
2837
|
-
|
|
2838
2826
|
[[package]]
|
|
2839
2827
|
name = "md-5"
|
|
2840
2828
|
version = "0.10.6"
|
|
@@ -3076,6 +3064,34 @@ dependencies = [
|
|
|
3076
3064
|
"vcpkg",
|
|
3077
3065
|
]
|
|
3078
3066
|
|
|
3067
|
+
[[package]]
|
|
3068
|
+
name = "openzeppelin-stylus"
|
|
3069
|
+
version = "0.2.0"
|
|
3070
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3071
|
+
checksum = "4a5bdc0165c9dd0628a88cbeb8c059b59fdb301156364c6759d838cbd4a581a2"
|
|
3072
|
+
dependencies = [
|
|
3073
|
+
"alloy-primitives",
|
|
3074
|
+
"alloy-sol-macro",
|
|
3075
|
+
"alloy-sol-macro-expander",
|
|
3076
|
+
"alloy-sol-macro-input",
|
|
3077
|
+
"alloy-sol-types",
|
|
3078
|
+
"keccak-const",
|
|
3079
|
+
"openzeppelin-stylus-proc",
|
|
3080
|
+
"stylus-sdk",
|
|
3081
|
+
]
|
|
3082
|
+
|
|
3083
|
+
[[package]]
|
|
3084
|
+
name = "openzeppelin-stylus-proc"
|
|
3085
|
+
version = "0.2.2"
|
|
3086
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3087
|
+
checksum = "f592a114a2f3bbfb29303435064b4444ea3d9c4283b667f551a8543e8da8896f"
|
|
3088
|
+
dependencies = [
|
|
3089
|
+
"convert_case",
|
|
3090
|
+
"proc-macro2",
|
|
3091
|
+
"quote",
|
|
3092
|
+
"syn 2.0.101",
|
|
3093
|
+
]
|
|
3094
|
+
|
|
3079
3095
|
[[package]]
|
|
3080
3096
|
name = "option-ext"
|
|
3081
3097
|
version = "0.2.0"
|
|
@@ -5596,6 +5612,7 @@ dependencies = [
|
|
|
5596
5612
|
"ethers",
|
|
5597
5613
|
"eyre",
|
|
5598
5614
|
"hex",
|
|
5615
|
+
"openzeppelin-stylus",
|
|
5599
5616
|
"stylus-sdk",
|
|
5600
5617
|
"tokio",
|
|
5601
5618
|
]
|
|
@@ -13,6 +13,7 @@ alloy-primitives = "=0.8.20"
|
|
|
13
13
|
alloy-sol-types = "=0.8.20"
|
|
14
14
|
stylus-sdk = "0.9.0"
|
|
15
15
|
hex = { version = "0.4", default-features = false }
|
|
16
|
+
openzeppelin-stylus = "0.2.0"
|
|
16
17
|
|
|
17
18
|
[dev-dependencies]
|
|
18
19
|
alloy-primitives = { version = "=0.8.20", features = ["sha3-keccak"] }
|
|
@@ -24,7 +25,8 @@ dotenv = "0.15.0"
|
|
|
24
25
|
|
|
25
26
|
[features]
|
|
26
27
|
default = ["mini-alloc"]
|
|
27
|
-
|
|
28
|
+
# stylus-sdk/export-abi will be enabled automatically.
|
|
29
|
+
export-abi = ["openzeppelin-stylus/export-abi"]
|
|
28
30
|
debug = ["stylus-sdk/debug"]
|
|
29
31
|
mini-alloc = ["stylus-sdk/mini-alloc"]
|
|
30
32
|
|
|
@@ -25,13 +25,25 @@ use stylus_sdk::{
|
|
|
25
25
|
stylus_core::log,
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
///
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
/// Import OpenZeppelin Ownable functionality
|
|
29
|
+
use openzeppelin_stylus::access::ownable::{self, Ownable, IOwnable};
|
|
30
|
+
|
|
31
|
+
/// Error types for the contract
|
|
32
|
+
#[derive(SolidityError, Debug)]
|
|
33
|
+
pub enum Error {
|
|
34
|
+
UnauthorizedAccount(ownable::OwnableUnauthorizedAccount),
|
|
35
|
+
InvalidOwner(ownable::OwnableInvalidOwner),
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
impl From<ownable::Error> for Error {
|
|
39
|
+
fn from(value: ownable::Error) -> Self {
|
|
40
|
+
match value {
|
|
41
|
+
ownable::Error::UnauthorizedAccount(e) => {
|
|
42
|
+
Error::UnauthorizedAccount(e)
|
|
43
|
+
}
|
|
44
|
+
ownable::Error::InvalidOwner(e) => Error::InvalidOwner(e),
|
|
33
45
|
}
|
|
34
|
-
}
|
|
46
|
+
}
|
|
35
47
|
}
|
|
36
48
|
|
|
37
49
|
// Define the GreetingChange event
|
|
@@ -44,7 +56,7 @@ sol! {
|
|
|
44
56
|
sol_storage! {
|
|
45
57
|
#[entrypoint]
|
|
46
58
|
pub struct YourContract {
|
|
47
|
-
|
|
59
|
+
Ownable ownable;
|
|
48
60
|
string greeting;
|
|
49
61
|
bool premium;
|
|
50
62
|
uint256 total_counter;
|
|
@@ -54,18 +66,16 @@ sol_storage! {
|
|
|
54
66
|
|
|
55
67
|
/// Declare that `YourContract` is a contract with the following external methods.
|
|
56
68
|
#[public]
|
|
69
|
+
#[implements(IOwnable<Error = Error>)]
|
|
57
70
|
impl YourContract {
|
|
58
|
-
|
|
59
|
-
pub fn
|
|
60
|
-
|
|
71
|
+
#[constructor]
|
|
72
|
+
pub fn constructor(&mut self, initial_owner: Address) -> Result<(), Error> {
|
|
73
|
+
// Initialize Ownable with the initial owner using OpenZeppelin pattern
|
|
74
|
+
self.ownable.constructor(initial_owner)?;
|
|
61
75
|
self.greeting.set_str("Building Unstoppable Apps!!!");
|
|
62
76
|
self.premium.set(false);
|
|
63
77
|
self.total_counter.set(U256::ZERO);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
/// Gets the owner address
|
|
67
|
-
pub fn owner(&self) -> Address {
|
|
68
|
-
self.owner.get()
|
|
78
|
+
Ok(())
|
|
69
79
|
}
|
|
70
80
|
|
|
71
81
|
/// Gets the current greeting
|
|
@@ -119,15 +129,14 @@ impl YourContract {
|
|
|
119
129
|
|
|
120
130
|
/// Function that allows the owner to withdraw all the Ether in the contract
|
|
121
131
|
/// The function can only be called by the owner of the contract
|
|
122
|
-
pub fn withdraw(&mut self) -> Result<(),
|
|
123
|
-
// Check if caller is owner
|
|
124
|
-
|
|
125
|
-
let owner: Address = self.owner.get();
|
|
126
|
-
require!(sender == owner, "Not the Owner");
|
|
132
|
+
pub fn withdraw(&mut self) -> Result<(), Error> {
|
|
133
|
+
// Check if caller is owner using OpenZeppelin's only_owner
|
|
134
|
+
self.ownable.only_owner()?;
|
|
127
135
|
|
|
128
136
|
// Get contract balance and transfer to owner using transfer_eth
|
|
129
|
-
let balance = self.vm().balance(
|
|
137
|
+
let balance = self.vm().balance(self.vm().contract_address());
|
|
130
138
|
if balance > U256::ZERO {
|
|
139
|
+
let owner = self.ownable.owner();
|
|
131
140
|
let _ = self.vm().transfer_eth(owner, balance);
|
|
132
141
|
}
|
|
133
142
|
|
|
@@ -142,6 +151,27 @@ impl YourContract {
|
|
|
142
151
|
}
|
|
143
152
|
}
|
|
144
153
|
|
|
154
|
+
/// Implementation of the IOwnable interface
|
|
155
|
+
#[public]
|
|
156
|
+
impl IOwnable for YourContract {
|
|
157
|
+
type Error = Error;
|
|
158
|
+
|
|
159
|
+
fn owner(&self) -> Address {
|
|
160
|
+
self.ownable.owner()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
fn transfer_ownership(
|
|
164
|
+
&mut self,
|
|
165
|
+
new_owner: Address,
|
|
166
|
+
) -> Result<(), Self::Error> {
|
|
167
|
+
Ok(self.ownable.transfer_ownership(new_owner)?)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
fn renounce_ownership(&mut self) -> Result<(), Self::Error> {
|
|
171
|
+
Ok(self.ownable.renounce_ownership()?)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
145
175
|
// #[cfg(test)]
|
|
146
176
|
// mod test {
|
|
147
177
|
// use super::*;
|