Arbitrum Stylus logo

Stylus by Example

Timelock Wallet

An Arbitrum Stylus version implementation of Solidity TimeLock.

  • TimeLock is a contract that publishes a transaction to be executed in the future. After a minimum waiting period, the transaction can be executed.
  • TimeLocks are commonly used in DAOs.

Here is the interface for TimeLock.

1interface ITimeLock {
2    function initialize() external;
3
4    function owner() external view returns (address);
5
6    function getTxId(address target, uint256 value, string calldata func, bytes calldata data, uint256 timestamp) external view returns (bytes32);
7
8    function deposit() external payable;
9
10    function queue(address target, uint256 value, string calldata func, bytes calldata data, uint256 timestamp) external;
11
12    function execute(address target, uint256 value, string calldata func, bytes calldata data, uint256 timestamp) external;
13
14    function cancel(address target, uint256 value, string calldata func, bytes calldata data, uint256 timestamp) external;
15
16    error AlreadyInitialized();
17
18    error NotOwnerError();
19
20    error AlreadyQueuedError(bytes32);
21
22    error TimestampNotInRangeError(uint256, uint256);
23
24    error NotQueuedError(bytes32);
25
26    error TimestampNotPassedError(uint256, uint256);
27
28    error TimestampExpiredError(uint256, uint256);
29
30    error TxFailedError();
31
32    event Queue(
33        bytes32 indexed txId,
34        address indexed target,
35        uint256 value,
36        string func,
37        bytes data,
38        uint256 timestamp
39    );
40
41    event Execute(
42        bytes32 indexed txId,
43        address indexed target,
44        uint256 value,
45        string func,
46        bytes data,
47        uint256 timestamp
48    );
49    
50    event Cancel(bytes32 indexed txId);
51}
1interface ITimeLock {
2    function initialize() external;
3
4    function owner() external view returns (address);
5
6    function getTxId(address target, uint256 value, string calldata func, bytes calldata data, uint256 timestamp) external view returns (bytes32);
7
8    function deposit() external payable;
9
10    function queue(address target, uint256 value, string calldata func, bytes calldata data, uint256 timestamp) external;
11
12    function execute(address target, uint256 value, string calldata func, bytes calldata data, uint256 timestamp) external;
13
14    function cancel(address target, uint256 value, string calldata func, bytes calldata data, uint256 timestamp) external;
15
16    error AlreadyInitialized();
17
18    error NotOwnerError();
19
20    error AlreadyQueuedError(bytes32);
21
22    error TimestampNotInRangeError(uint256, uint256);
23
24    error NotQueuedError(bytes32);
25
26    error TimestampNotPassedError(uint256, uint256);
27
28    error TimestampExpiredError(uint256, uint256);
29
30    error TxFailedError();
31
32    event Queue(
33        bytes32 indexed txId,
34        address indexed target,
35        uint256 value,
36        string func,
37        bytes data,
38        uint256 timestamp
39    );
40
41    event Execute(
42        bytes32 indexed txId,
43        address indexed target,
44        uint256 value,
45        string func,
46        bytes data,
47        uint256 timestamp
48    );
49    
50    event Cancel(bytes32 indexed txId);
51}

Example implementation of a Timelock contract written in Rust.

src/lib.rs

1// Allow `cargo stylus export-abi` to generate a main function if the "export-abi" feature is enabled.
2#![cfg_attr(not(feature = "export-abi"), no_main)]
3extern crate alloc;
4
5use sha3::{Digest, Keccak256};
6use alloc::string::String;
7use alloy_primitives::{Address, FixedBytes, U256};
8use alloy_sol_types::{sol, sol_data::{Address as SOLAddress, Bytes as SOLBytes, *}, SolType};
9// Import items from the SDK. The prelude contains common traits and macros.
10use stylus_sdk::{abi::Bytes, block, call::{call, Call}, evm, msg, prelude::*};
11
12// Define the types of the contract's storage.
13type TxIdHashType = (SOLAddress, Uint<256>, SOLBytes, SOLBytes, Uint<256>);
14
15sol!{
16    error AlreadyInitialized();
17    error NotOwnerError();
18    error AlreadyQueuedError(bytes32 txId);
19    error TimestampNotInRangeError(uint256 blockTimestamp, uint256 timestamp);
20    error NotQueuedError(bytes32 txId);
21    error TimestampNotPassedError(uint256 blockTimestamp, uint256 timestamp);
22    error TimestampExpiredError(uint256 blockTimestamp, uint256 expiresAt);
23    error TxFailedError();
24
25    event Queue(
26        bytes32 indexed txId,
27        address indexed target,
28        uint256 value,
29        string func,
30        bytes data,
31        uint256 timestamp
32    );
33    event Execute(
34        bytes32 indexed txId,
35        address indexed target,
36        uint256 value,
37        string func,
38        bytes data,
39        uint256 timestamp
40    );
41    event Cancel(bytes32 indexed txId);
42}
43
44// Define persistent storage using the Solidity ABI.
45// `TimeLock` will be the entrypoint for the contract.
46sol_storage! {
47    // Define the contract's storage.
48    #[entrypoint]
49    pub struct TimeLock {
50        address owner;
51        mapping(bytes32 => bool) queued;
52    }
53}
54
55// Error types for the TimeLock contract
56#[derive(SolidityError)]
57pub enum TimeLockError {
58    // Error for when the contract is already initialized.
59    AlreadyInitialized(AlreadyInitialized),
60    // Error for when the sender is not the owner
61    NotOwnerError(NotOwnerError),
62    // Error for when the transaction is already queued
63    AlreadyQueuedError(AlreadyQueuedError),
64    // Error for when the timestamp is not in the range
65    TimestampNotInRangeError(TimestampNotInRangeError),
66    // Error for when the transaction is not queued
67    NotQueuedError(NotQueuedError),
68    // Error for when the timestamp has not yet passed
69    TimestampNotPassedError(TimestampNotPassedError),
70    // Error for when the timestamp has expired
71    TimestampExpiredError(TimestampExpiredError),
72    // Error for when a transaction fails
73    TxFailedError(TxFailedError),
74}
75
76// Marks `TimeLock` as a contract with the specified external methods
77#[public]
78impl TimeLock  {
79
80    // Minimum delay allowed for a transaction
81    pub const MIN_DELAY: u64 = 10;
82    // Maximum delay allowed for a transaction
83    pub const MAX_DELAY: u64 = 1000;
84    // Grace period after the maximum delay
85    pub const GRACE_PERIOD: u64 = 1000;
86
87    pub fn initialize(&mut self) -> Result<(), TimeLockError> {
88        if self.owner.get() != Address::default() {
89            return Err(TimeLockError::AlreadyInitialized(AlreadyInitialized{}))
90        }
91        self.owner.set(msg::sender());
92        Ok(())
93    }
94
95    pub fn owner(&self) -> Address {
96        self.owner.get()
97    }
98
99    // Function to generate a transaction ID
100    pub fn get_tx_id(
101        &self, 
102        target: Address, // Target address for the transaction
103        value: U256, // Value to be transferred
104        func: String, // Function name to be called
105        data: Bytes, // Data to be passed to the function
106        timestamp: U256, // Timestamp for the transaction
107    ) -> FixedBytes<32>{
108        
109        // Package the transaction data
110        let tx_hash_data = (target, value, func, data, timestamp);
111        // Encode the transaction data using ABI encoding
112        let tx_hash_bytes = TxIdHashType::abi_encode_sequence(&tx_hash_data);
113        // Initialize a new Keccak256 hasher
114        let mut hasher = Keccak256::new();
115        // Update the hasher with the encoded bytes
116        hasher.update(tx_hash_bytes);
117        // Finalize the hash computation
118        let result = hasher.finalize();
119        // Convert the hash result to a vector
120        let result_vec = result.to_vec();
121        // Create a FixedBytes<32> instance from the result vector
122        // This is used as the transaction ID
123        alloy_primitives::FixedBytes::<32>::from_slice(&result_vec)
124    }
125
126    // The `deposit` method is payable, so it can receive funds.
127    #[payable]
128    pub fn deposit(&self) {
129    }
130
131    // Function to queue a transaction for execution
132    pub fn queue(
133        &mut self,
134        target: Address, // Target address for the transaction
135        value: U256, // Value to be transferred
136        func: String, // Function name to be called
137        data: Bytes, // Data to be passed to the function
138        timestamp: U256, // Timestamp for the transaction
139    ) -> Result<(), TimeLockError> {
140        // Check if the caller is the owner of the contract
141        if self.owner.get() != msg::sender() {
142            // If not, return an error indicating the caller is not the owner
143            return Err(TimeLockError::NotOwnerError(NotOwnerError{}));
144        };
145        
146        // Calculate a transaction ID using the provided parameters
147        let tx_id = self.get_tx_id(target, value, func.clone(), data.clone(), timestamp);
148        // Check if the transaction is already queued
149        if self.queued.get(tx_id) {
150            return Err(TimeLockError::AlreadyQueuedError(AlreadyQueuedError{txId: tx_id.into()}));
151        }
152
153        // Check if the provided timestamp is within the allowed range
154        if timestamp < U256::from(block::timestamp()) + U256::from(TimeLock::MIN_DELAY)
155            || timestamp > U256::from(block::timestamp()) + U256::from(TimeLock::MAX_DELAY)
156        {
157            return Err(TimeLockError::TimestampNotInRangeError(TimestampNotInRangeError{blockTimestamp: U256::from(block::timestamp()),timestamp: timestamp}));
158        }
159
160        // Set the transaction as queued in the contract's state
161        let mut queue_id = self.queued.setter(tx_id);
162        queue_id.set(true);
163        // Log the Queue event
164        evm::log(Queue {
165            txId: tx_id.into(),
166            target,
167            value: value,
168            func: func,
169            data: data.to_vec().into(),
170            timestamp: timestamp,
171        });
172        // If all checks pass and the transaction is successfully queued, return Ok
173        Ok(())
174    }
175
176    // Function to execute a queued transaction
177    pub fn execute(
178        &mut self,
179        target: Address, // Target address for the transaction
180        value: U256, // Value to be transferred
181        func: String, // Function name to be called
182        data: Bytes, // Data to be passed to the function
183        timestamp: U256, // Timestamp for the transaction
184    ) -> Result<(), TimeLockError> {
185        // Check if the caller is the owner of the contract
186        if self.owner.get() != msg::sender() {
187            // If not, return an error indicating the caller is not the owner
188            return Err(TimeLockError::NotOwnerError(NotOwnerError{}));
189        };
190        
191        // Calculate a transaction ID using the provided parameters
192        let tx_id = self.get_tx_id(target, value, func.clone(), data.clone(), timestamp);
193        // Check if the transaction is not queued
194        if !self.queued.get(tx_id) {
195            return Err(TimeLockError::NotQueuedError(NotQueuedError{txId: tx_id.into()}));
196        }
197        
198        // ----|-------------------|-------
199        //  timestamp    timestamp + grace period
200
201        // Check if the timestamp has passed
202        if U256::from(block::timestamp()) < timestamp {
203            return Err(TimeLockError::TimestampNotPassedError(TimestampNotPassedError{blockTimestamp: U256::from(block::timestamp()), timestamp: timestamp}));
204        }
205        
206        // Check if the timestamp has expired
207        if U256::from(block::timestamp()) > timestamp + U256::from(TimeLock::GRACE_PERIOD) {
208            return Err(TimeLockError::TimestampExpiredError(TimestampExpiredError{blockTimestamp: U256::from(block::timestamp()), expiresAt: timestamp + U256::from(TimeLock::GRACE_PERIOD)}));
209        }
210        
211        // Set the transaction as not queued in the contract's state
212        let mut queue_id = self.queued.setter(tx_id);
213        queue_id.set(false);
214
215        // Clone the data variable to ensure its lifetime is long enough
216        // let cloned_data: Vec<u8> = data.clone().into();
217        
218        // Prepare calldata
219        let mut hasher = Keccak256::new();
220        hasher.update(func.as_bytes());
221        // Get function selector
222        let hashed_function_selector = hasher.finalize();
223        // Combine function selector and input data
224        let calldata = [&hashed_function_selector[..4], &data].concat();
225        
226        // Call the target contract with the provided parameters
227        match call(Call::new_in(self).value(value), target, &calldata) {
228            // Log the transaction execution if successful
229            Ok(_) => {
230                evm::log(Execute {
231                    txId: tx_id.into(),
232                    target,
233                    value: value,
234                    func: func,
235                    data: data.to_vec().into(),
236                    timestamp: timestamp,
237                });
238                Ok(())
239            },
240            // Return an error if the transaction fails
241            Err(_) => Err(TimeLockError::TxFailedError(TxFailedError{})),
242        }
243    }
244
245    // Function to cancel a queued transaction
246    pub fn cancel(
247        &mut self,
248        target: Address,
249        value: U256,
250        func: String,
251        data: Bytes,
252        timestamp: U256,
253    ) -> Result<(), TimeLockError> {
254        // Check if the caller is the owner of the contract
255        if self.owner.get() != msg::sender() {
256            // If not, return an error indicating the caller is not the owner
257            return Err(TimeLockError::NotOwnerError(NotOwnerError{}));
258        };
259
260        // Calculate a transaction ID using the provided parameters
261        let tx_id = self.get_tx_id(target, value, func, data, timestamp);
262        // Check if the transaction is not queued
263        if !self.queued.get(tx_id) {
264            return Err(TimeLockError::NotQueuedError(NotQueuedError{txId: tx_id.into()}));
265        }
266
267        // Set the transaction as not queued in the contract's state
268        let mut queue_id = self.queued.setter(tx_id);
269        queue_id.set(false);
270
271        // Log the transaction cancellation
272        evm::log(Cancel {
273            txId: tx_id.into(),
274        });
275
276        // Return Ok if the transaction is successfully cancelled
277        Ok(())
278    }
279
280    
281}
1// Allow `cargo stylus export-abi` to generate a main function if the "export-abi" feature is enabled.
2#![cfg_attr(not(feature = "export-abi"), no_main)]
3extern crate alloc;
4
5use sha3::{Digest, Keccak256};
6use alloc::string::String;
7use alloy_primitives::{Address, FixedBytes, U256};
8use alloy_sol_types::{sol, sol_data::{Address as SOLAddress, Bytes as SOLBytes, *}, SolType};
9// Import items from the SDK. The prelude contains common traits and macros.
10use stylus_sdk::{abi::Bytes, block, call::{call, Call}, evm, msg, prelude::*};
11
12// Define the types of the contract's storage.
13type TxIdHashType = (SOLAddress, Uint<256>, SOLBytes, SOLBytes, Uint<256>);
14
15sol!{
16    error AlreadyInitialized();
17    error NotOwnerError();
18    error AlreadyQueuedError(bytes32 txId);
19    error TimestampNotInRangeError(uint256 blockTimestamp, uint256 timestamp);
20    error NotQueuedError(bytes32 txId);
21    error TimestampNotPassedError(uint256 blockTimestamp, uint256 timestamp);
22    error TimestampExpiredError(uint256 blockTimestamp, uint256 expiresAt);
23    error TxFailedError();
24
25    event Queue(
26        bytes32 indexed txId,
27        address indexed target,
28        uint256 value,
29        string func,
30        bytes data,
31        uint256 timestamp
32    );
33    event Execute(
34        bytes32 indexed txId,
35        address indexed target,
36        uint256 value,
37        string func,
38        bytes data,
39        uint256 timestamp
40    );
41    event Cancel(bytes32 indexed txId);
42}
43
44// Define persistent storage using the Solidity ABI.
45// `TimeLock` will be the entrypoint for the contract.
46sol_storage! {
47    // Define the contract's storage.
48    #[entrypoint]
49    pub struct TimeLock {
50        address owner;
51        mapping(bytes32 => bool) queued;
52    }
53}
54
55// Error types for the TimeLock contract
56#[derive(SolidityError)]
57pub enum TimeLockError {
58    // Error for when the contract is already initialized.
59    AlreadyInitialized(AlreadyInitialized),
60    // Error for when the sender is not the owner
61    NotOwnerError(NotOwnerError),
62    // Error for when the transaction is already queued
63    AlreadyQueuedError(AlreadyQueuedError),
64    // Error for when the timestamp is not in the range
65    TimestampNotInRangeError(TimestampNotInRangeError),
66    // Error for when the transaction is not queued
67    NotQueuedError(NotQueuedError),
68    // Error for when the timestamp has not yet passed
69    TimestampNotPassedError(TimestampNotPassedError),
70    // Error for when the timestamp has expired
71    TimestampExpiredError(TimestampExpiredError),
72    // Error for when a transaction fails
73    TxFailedError(TxFailedError),
74}
75
76// Marks `TimeLock` as a contract with the specified external methods
77#[public]
78impl TimeLock  {
79
80    // Minimum delay allowed for a transaction
81    pub const MIN_DELAY: u64 = 10;
82    // Maximum delay allowed for a transaction
83    pub const MAX_DELAY: u64 = 1000;
84    // Grace period after the maximum delay
85    pub const GRACE_PERIOD: u64 = 1000;
86
87    pub fn initialize(&mut self) -> Result<(), TimeLockError> {
88        if self.owner.get() != Address::default() {
89            return Err(TimeLockError::AlreadyInitialized(AlreadyInitialized{}))
90        }
91        self.owner.set(msg::sender());
92        Ok(())
93    }
94
95    pub fn owner(&self) -> Address {
96        self.owner.get()
97    }
98
99    // Function to generate a transaction ID
100    pub fn get_tx_id(
101        &self, 
102        target: Address, // Target address for the transaction
103        value: U256, // Value to be transferred
104        func: String, // Function name to be called
105        data: Bytes, // Data to be passed to the function
106        timestamp: U256, // Timestamp for the transaction
107    ) -> FixedBytes<32>{
108        
109        // Package the transaction data
110        let tx_hash_data = (target, value, func, data, timestamp);
111        // Encode the transaction data using ABI encoding
112        let tx_hash_bytes = TxIdHashType::abi_encode_sequence(&tx_hash_data);
113        // Initialize a new Keccak256 hasher
114        let mut hasher = Keccak256::new();
115        // Update the hasher with the encoded bytes
116        hasher.update(tx_hash_bytes);
117        // Finalize the hash computation
118        let result = hasher.finalize();
119        // Convert the hash result to a vector
120        let result_vec = result.to_vec();
121        // Create a FixedBytes<32> instance from the result vector
122        // This is used as the transaction ID
123        alloy_primitives::FixedBytes::<32>::from_slice(&result_vec)
124    }
125
126    // The `deposit` method is payable, so it can receive funds.
127    #[payable]
128    pub fn deposit(&self) {
129    }
130
131    // Function to queue a transaction for execution
132    pub fn queue(
133        &mut self,
134        target: Address, // Target address for the transaction
135        value: U256, // Value to be transferred
136        func: String, // Function name to be called
137        data: Bytes, // Data to be passed to the function
138        timestamp: U256, // Timestamp for the transaction
139    ) -> Result<(), TimeLockError> {
140        // Check if the caller is the owner of the contract
141        if self.owner.get() != msg::sender() {
142            // If not, return an error indicating the caller is not the owner
143            return Err(TimeLockError::NotOwnerError(NotOwnerError{}));
144        };
145        
146        // Calculate a transaction ID using the provided parameters
147        let tx_id = self.get_tx_id(target, value, func.clone(), data.clone(), timestamp);
148        // Check if the transaction is already queued
149        if self.queued.get(tx_id) {
150            return Err(TimeLockError::AlreadyQueuedError(AlreadyQueuedError{txId: tx_id.into()}));
151        }
152
153        // Check if the provided timestamp is within the allowed range
154        if timestamp < U256::from(block::timestamp()) + U256::from(TimeLock::MIN_DELAY)
155            || timestamp > U256::from(block::timestamp()) + U256::from(TimeLock::MAX_DELAY)
156        {
157            return Err(TimeLockError::TimestampNotInRangeError(TimestampNotInRangeError{blockTimestamp: U256::from(block::timestamp()),timestamp: timestamp}));
158        }
159
160        // Set the transaction as queued in the contract's state
161        let mut queue_id = self.queued.setter(tx_id);
162        queue_id.set(true);
163        // Log the Queue event
164        evm::log(Queue {
165            txId: tx_id.into(),
166            target,
167            value: value,
168            func: func,
169            data: data.to_vec().into(),
170            timestamp: timestamp,
171        });
172        // If all checks pass and the transaction is successfully queued, return Ok
173        Ok(())
174    }
175
176    // Function to execute a queued transaction
177    pub fn execute(
178        &mut self,
179        target: Address, // Target address for the transaction
180        value: U256, // Value to be transferred
181        func: String, // Function name to be called
182        data: Bytes, // Data to be passed to the function
183        timestamp: U256, // Timestamp for the transaction
184    ) -> Result<(), TimeLockError> {
185        // Check if the caller is the owner of the contract
186        if self.owner.get() != msg::sender() {
187            // If not, return an error indicating the caller is not the owner
188            return Err(TimeLockError::NotOwnerError(NotOwnerError{}));
189        };
190        
191        // Calculate a transaction ID using the provided parameters
192        let tx_id = self.get_tx_id(target, value, func.clone(), data.clone(), timestamp);
193        // Check if the transaction is not queued
194        if !self.queued.get(tx_id) {
195            return Err(TimeLockError::NotQueuedError(NotQueuedError{txId: tx_id.into()}));
196        }
197        
198        // ----|-------------------|-------
199        //  timestamp    timestamp + grace period
200
201        // Check if the timestamp has passed
202        if U256::from(block::timestamp()) < timestamp {
203            return Err(TimeLockError::TimestampNotPassedError(TimestampNotPassedError{blockTimestamp: U256::from(block::timestamp()), timestamp: timestamp}));
204        }
205        
206        // Check if the timestamp has expired
207        if U256::from(block::timestamp()) > timestamp + U256::from(TimeLock::GRACE_PERIOD) {
208            return Err(TimeLockError::TimestampExpiredError(TimestampExpiredError{blockTimestamp: U256::from(block::timestamp()), expiresAt: timestamp + U256::from(TimeLock::GRACE_PERIOD)}));
209        }
210        
211        // Set the transaction as not queued in the contract's state
212        let mut queue_id = self.queued.setter(tx_id);
213        queue_id.set(false);
214
215        // Clone the data variable to ensure its lifetime is long enough
216        // let cloned_data: Vec<u8> = data.clone().into();
217        
218        // Prepare calldata
219        let mut hasher = Keccak256::new();
220        hasher.update(func.as_bytes());
221        // Get function selector
222        let hashed_function_selector = hasher.finalize();
223        // Combine function selector and input data
224        let calldata = [&hashed_function_selector[..4], &data].concat();
225        
226        // Call the target contract with the provided parameters
227        match call(Call::new_in(self).value(value), target, &calldata) {
228            // Log the transaction execution if successful
229            Ok(_) => {
230                evm::log(Execute {
231                    txId: tx_id.into(),
232                    target,
233                    value: value,
234                    func: func,
235                    data: data.to_vec().into(),
236                    timestamp: timestamp,
237                });
238                Ok(())
239            },
240            // Return an error if the transaction fails
241            Err(_) => Err(TimeLockError::TxFailedError(TxFailedError{})),
242        }
243    }
244
245    // Function to cancel a queued transaction
246    pub fn cancel(
247        &mut self,
248        target: Address,
249        value: U256,
250        func: String,
251        data: Bytes,
252        timestamp: U256,
253    ) -> Result<(), TimeLockError> {
254        // Check if the caller is the owner of the contract
255        if self.owner.get() != msg::sender() {
256            // If not, return an error indicating the caller is not the owner
257            return Err(TimeLockError::NotOwnerError(NotOwnerError{}));
258        };
259
260        // Calculate a transaction ID using the provided parameters
261        let tx_id = self.get_tx_id(target, value, func, data, timestamp);
262        // Check if the transaction is not queued
263        if !self.queued.get(tx_id) {
264            return Err(TimeLockError::NotQueuedError(NotQueuedError{txId: tx_id.into()}));
265        }
266
267        // Set the transaction as not queued in the contract's state
268        let mut queue_id = self.queued.setter(tx_id);
269        queue_id.set(false);
270
271        // Log the transaction cancellation
272        evm::log(Cancel {
273            txId: tx_id.into(),
274        });
275
276        // Return Ok if the transaction is successfully cancelled
277        Ok(())
278    }
279
280    
281}

Cargo.toml

1[package]
2name = "stylus-timelock-example"
3version = "0.1.0"
4edition = "2021"
5
6[dependencies]
7alloy-primitives = "=0.7.6"
8alloy-sol-types = "=0.7.6"
9mini-alloc = "0.4.2"
10stylus-sdk = "0.6.0"
11hex = "0.4.3"
12sha3 = "0.10.8"
13
14[features]
15export-abi = ["stylus-sdk/export-abi"]
16debug = ["stylus-sdk/debug"]
17
18[lib]
19crate-type = ["lib", "cdylib"]
20
21[profile.release]
22codegen-units = 1
23strip = true
24lto = true
25panic = "abort"
26opt-level = "s"
1[package]
2name = "stylus-timelock-example"
3version = "0.1.0"
4edition = "2021"
5
6[dependencies]
7alloy-primitives = "=0.7.6"
8alloy-sol-types = "=0.7.6"
9mini-alloc = "0.4.2"
10stylus-sdk = "0.6.0"
11hex = "0.4.3"
12sha3 = "0.10.8"
13
14[features]
15export-abi = ["stylus-sdk/export-abi"]
16debug = ["stylus-sdk/debug"]
17
18[lib]
19crate-type = ["lib", "cdylib"]
20
21[profile.release]
22codegen-units = 1
23strip = true
24lto = true
25panic = "abort"
26opt-level = "s"