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.
Files changed (27) hide show
  1. package/package.json +1 -1
  2. package/templates/base/.gitignore.template.mjs +2 -2
  3. package/templates/base/dist/cli.js +683 -0
  4. package/templates/base/dist/cli.js.map +1 -0
  5. package/templates/base/package.json +1 -0
  6. package/templates/base/packages/nextjs/.gitignore.template.mjs +3 -3
  7. package/templates/base/packages/nextjs/scaffold.config.ts +2 -2
  8. package/templates/base/packages/stylus/.env.example +5 -2
  9. package/templates/base/packages/stylus/.gitignore.template.mjs +3 -3
  10. package/templates/base/packages/stylus/package.json +0 -1
  11. package/templates/base/packages/stylus/scripts/deploy.ts +29 -12
  12. package/templates/base/packages/stylus/scripts/deploy_contract.ts +34 -43
  13. package/templates/base/packages/stylus/scripts/deploy_wrapper.ts +4 -44
  14. package/templates/base/packages/stylus/scripts/export_abi.ts +13 -13
  15. package/templates/base/packages/stylus/scripts/utils/command.ts +18 -31
  16. package/templates/base/packages/stylus/scripts/utils/contract.ts +23 -14
  17. package/templates/base/packages/stylus/scripts/utils/deployment.ts +169 -45
  18. package/templates/base/packages/stylus/scripts/utils/network.ts +27 -7
  19. package/templates/base/packages/stylus/scripts/utils/type.ts +13 -10
  20. package/templates/base/packages/stylus/your-contract/Cargo.lock +35 -18
  21. package/templates/base/packages/stylus/your-contract/Cargo.toml +3 -1
  22. package/templates/base/packages/stylus/your-contract/src/lib.rs +51 -21
  23. package/templates/base/readme.md +208 -43
  24. package/templates/base/yarn.lock +1 -2
  25. package/templates/base/packages/stylus/README.md +0 -263
  26. package/templates/base/packages/stylus/header.png +0 -0
  27. 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 addresses.json in the deployment directory.
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 saveDeployedAddress(config: DeploymentConfig) {
66
+ export function saveDeployment(
67
+ config: DeploymentConfig,
68
+ deploymentInfo: DeploymentData,
69
+ ) {
65
70
  try {
66
- const addressesPath = path.resolve(config.deploymentDir, "addresses.json");
67
- let addresses: Record<string, any> = {};
68
- if (fs.existsSync(addressesPath)) {
69
- const content = fs.readFileSync(addressesPath, "utf8");
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
- addresses = JSON.parse(content);
111
+ deployments = JSON.parse(content);
72
112
  } catch (e) {
73
113
  console.warn(
74
- `⚠️ Could not parse existing addresses.json, will overwrite. Error: ${e}`,
114
+ `⚠️ Could not parse existing ${chainId}_latest.json, will overwrite. Error: ${e}`,
75
115
  );
76
116
  }
77
117
  }
78
118
 
79
- // Save both address and chain ID
80
- addresses[config.contractName] = {
81
- address: config.contractAddress || "",
82
- chainId: (config.chain?.id || arbitrumNitro.id).toString(),
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(addressesPath, JSON.stringify(addresses, null, 2));
86
- console.log(`💾 Saved deployed address and chain ID to ${addressesPath}`);
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 address: ${e}`);
129
+ console.error(`❌ Failed to save deployed contract: ${e}`);
89
130
  }
90
131
  }
91
132
 
92
- export function printDeployedAddresses(deploymentDir: string): void {
93
- const addressesPath = path.resolve(deploymentDir, "addresses.json");
94
- if (fs.existsSync(addressesPath)) {
95
- const addresses = JSON.parse(fs.readFileSync(addressesPath, "utf8"));
96
- console.log(`📦 Deployed contract addresses (${addressesPath}):`);
97
-
98
- // Format the output to show contract name, address, and chain ID clearly
99
- Object.entries(addresses).forEach(([contractName, contractData]) => {
100
- const data = contractData as {
101
- address: string;
102
- chainId: string;
103
- };
104
- console.log(` ${contractName}:`);
105
- console.log(` Address: ${data.address}`);
106
- console.log(` Chain ID: ${data.chainId}`);
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 addresses.json in the deployment directory.
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
- ): { address: string; chainId: string } | undefined {
119
- const addressesPath = path.resolve(deploymentDir, "addresses.json");
120
- if (fs.existsSync(addressesPath)) {
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 addresses = JSON.parse(fs.readFileSync(addressesPath, "utf8"));
123
- if (addresses[contractName]?.address) {
124
- return addresses[contractName] as {
125
- address: string;
126
- chainId: string;
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
- return process.env["PRIVATE_KEY_MAINNET"] || "";
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
- return process.env["PRIVATE_KEY_SEPOLIA"] || "";
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: string | undefined;
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.25"
385
+ version = "0.8.20"
386
386
  source = "registry+https://github.com/rust-lang/crates.io-index"
387
- checksum = "e10ae8e9a91d328ae954c22542415303919aabe976fe7a92eb06db1b68fd59f2"
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.25"
399
+ version = "0.8.20"
400
400
  source = "registry+https://github.com/rust-lang/crates.io-index"
401
- checksum = "83ad5da86c127751bc607c174d6c9fe9b85ef0889a9ca0c641735d77d4f98f26"
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.25"
417
+ version = "0.8.20"
418
418
  source = "registry+https://github.com/rust-lang/crates.io-index"
419
- checksum = "ba3d30f0d3f9ba3b7686f3ff1de9ee312647aac705604417a2f40c604f409a9e"
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
- export-abi = ["stylus-sdk/export-abi"]
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
- /// Helper macro for require-like assertions
29
- macro_rules! require {
30
- ($condition:expr, $message:expr) => {
31
- if !$condition {
32
- panic!($message);
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
- address owner;
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
- /// Constructor equivalent - initializes the contract
59
- pub fn init(&mut self, owner: Address) {
60
- self.owner.set(owner);
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<(), Vec<u8>> {
123
- // Check if caller is owner
124
- let sender: Address = self.vm().msg_sender() ;
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(sender);
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::*;