An Arbitrum Stylus version implementation of Solidity English Auction.
Here is the interface for English Auction.
1interface IEnglishAuction {
2 function nft() external view returns (address);
3
4 function nftId() external view returns (uint256);
5
6 function seller() external view returns (address);
7
8 function endAt() external view returns (uint256);
9
10 function started() external view returns (bool);
11
12 function ended() external view returns (bool);
13
14 function highestBidder() external view returns (address);
15
16 function highestBid() external view returns (uint256);
17
18 function bids(address bidder) external view returns (uint256);
19
20 function initialize(address nft, uint256 nft_id, uint256 starting_bid) external;
21
22 function start() external;
23
24 function bid() external payable;
25
26 function withdraw() external;
27
28 function end() external;
29
30 error AlreadyInitialized();
31
32 error AlreadyStarted();
33
34 error NotSeller();
35
36 error AuctionEnded();
37
38 error BidTooLow();
39
40 error NotStarted();
41
42 error NotEnded();
43}
1interface IEnglishAuction {
2 function nft() external view returns (address);
3
4 function nftId() external view returns (uint256);
5
6 function seller() external view returns (address);
7
8 function endAt() external view returns (uint256);
9
10 function started() external view returns (bool);
11
12 function ended() external view returns (bool);
13
14 function highestBidder() external view returns (address);
15
16 function highestBid() external view returns (uint256);
17
18 function bids(address bidder) external view returns (uint256);
19
20 function initialize(address nft, uint256 nft_id, uint256 starting_bid) external;
21
22 function start() external;
23
24 function bid() external payable;
25
26 function withdraw() external;
27
28 function end() external;
29
30 error AlreadyInitialized();
31
32 error AlreadyStarted();
33
34 error NotSeller();
35
36 error AuctionEnded();
37
38 error BidTooLow();
39
40 error NotStarted();
41
42 error NotEnded();
43}
Example implementation of a English Auction contract written in Rust.
1// Allow `cargo stylus export-abi` to generate a main function.
2#![cfg_attr(not(feature = "export-abi"), no_main)]
3extern crate alloc;
4
5use std::borrow::BorrowMut;
6
7/// Import items from the SDK. The prelude contains common traits and macros.
8use stylus_sdk::{alloy_primitives::{Address, U256}, block, call::{transfer_eth, Call}, contract, evm, msg, prelude::*};
9use alloy_sol_types::sol;
10
11// Import the IERC721 interface.
12sol_interface! {
13 interface IERC721 {
14 // Required methods.
15 function safeTransferFrom(address from, address to, uint256 token_id) external;
16 function transferFrom(address, address, uint256) external;
17 }
18}
19
20// Define the events and errors for the contract.
21sol!{
22 // Define the events for the contract.
23 event Start(); // Start the auction.
24 event Bid(address indexed sender, uint256 amount); // Bid on the auction.
25 event Withdraw(address indexed bidder, uint256 amount); // Withdraw a bid.
26 event End(address winner, uint256 amount); // End the auction.
27
28 // Define the errors for the contract.
29 error AlreadyInitialized(); // The contract has already been initialized.
30 error AlreadyStarted(); // The auction has already started.
31 error NotSeller(); // The sender is not the seller.
32 error AuctionEnded(); // The auction has ended.
33 error BidTooLow(); // The bid is too low.
34 error NotStarted(); // The auction has not started.
35 error NotEnded(); // The auction has not ended.
36}
37
38
39#[derive(SolidityError)]
40pub enum EnglishAuctionError {
41 // Define the errors for the contract.
42 AlreadyInitialized(AlreadyInitialized),
43 AlreadyStarted(AlreadyStarted),
44 NotSeller(NotSeller),
45 AuctionEnded(AuctionEnded),
46 BidTooLow(BidTooLow),
47 NotStarted(NotStarted),
48 NotEnded(NotEnded),
49}
50
51// Define some persistent storage using the Solidity ABI.
52// `Counter` will be the entrypoint.
53sol_storage! {
54 #[entrypoint]
55 pub struct EnglishAuction {
56 address nft_address; // The address of the NFT contract.
57 uint256 nft_id; // The ID of the NFT.
58
59 address seller; // The address of the seller.
60 uint256 end_at; // The end time of the auction.
61 bool started; // The auction has started or not.
62 bool ended; // The auction has ended or not.
63
64 address highest_bidder; // The address of the highest bidder.
65 uint256 highest_bid; // The highest bid.
66 mapping(address => uint256) bids; // The bids of the bidders.
67 }
68}
69
70/// Declare that `Counter` is a contract with the following external methods.
71#[public]
72impl EnglishAuction {
73 pub const ONE_DAY: u64 = 86400; // 1 day = 24 hours * 60 minutes * 60 seconds = 86400 seconds.
74
75 // Get nft address
76 pub fn nft(&self) -> Result<Address, EnglishAuctionError> {
77 Ok(self.nft_address.get())
78 }
79
80 // Get nft id
81 pub fn nft_id(&self) -> Result<U256, EnglishAuctionError> {
82 Ok(self.nft_id.get())
83 }
84 // Get seller address
85 pub fn seller(&self) -> Result<Address, EnglishAuctionError> {
86 Ok(self.seller.get())
87 }
88
89 // Get end time
90 pub fn end_at(&self) -> Result<U256, EnglishAuctionError> {
91 Ok(self.end_at.get())
92 }
93
94 // Get started status
95 pub fn started(&self) -> Result<bool, EnglishAuctionError> {
96 Ok(self.started.get())
97 }
98
99 // Get ended status
100 pub fn ended(&self) -> Result<bool, EnglishAuctionError> {
101 Ok(self.ended.get())
102 }
103
104 // Get highest bidder address
105 pub fn highest_bidder(&self) -> Result<Address, EnglishAuctionError> {
106 Ok(self.highest_bidder.get())
107 }
108
109 // Get highest bid amount
110 pub fn highest_bid(&self) -> Result<U256, EnglishAuctionError> {
111 Ok(self.highest_bid.get())
112 }
113
114 // Get bid amount of a bidder
115 pub fn bids(&self, bidder: Address) -> Result<U256, EnglishAuctionError> {
116 Ok(self.bids.getter(bidder).get())
117 }
118
119 // Initialize program
120 pub fn initialize(&mut self, nft: Address, nft_id: U256, starting_bid: U256) -> Result<(), EnglishAuctionError> {
121 // Check if the contract has already been initialized.
122 if self.seller.get() != Address::default() {
123 // Return an error if the contract has already been initialized.
124 return Err(EnglishAuctionError::AlreadyInitialized(AlreadyInitialized{}));
125 }
126
127 // Initialize the contract with the NFT address, the NFT ID, the seller, and the starting bid.
128 self.nft_address.set(nft);
129 self.nft_id.set(nft_id);
130 self.seller.set(msg::sender());
131 self.highest_bid.set(starting_bid);
132 Ok(())
133 }
134
135 pub fn start(&mut self) -> Result<(), EnglishAuctionError> {
136 // Check if the auction has already started.
137 if self.started.get() {
138 return Err(EnglishAuctionError::AlreadyStarted(AlreadyStarted{}));
139 }
140
141 // Check if the sender is the seller.
142 if self.seller.get() != msg::sender() {
143 // Return an error if the sender is the seller.
144 return Err(EnglishAuctionError::NotSeller(NotSeller{}));
145 }
146
147 // Create a new instance of the IERC721 interface.
148 let nft = IERC721::new(*self.nft_address);
149 // Get the NFT ID.
150 let nft_id = self.nft_id.get();
151
152 // Transfer the NFT to the contract.
153 let config = Call::new_in(self);
154 let result = nft.transfer_from(config, msg::sender(), contract::address(), nft_id);
155
156 match result {
157 // If the transfer is successful, start the auction.
158 Ok(_) => {
159 self.started.set(true);
160 // Set the end time of the auction to 7 days from now.
161 self.end_at.set(U256::from(block::timestamp() + 7 * Self::ONE_DAY));
162 // Log the start event.
163 evm::log(Start {});
164 Ok(())
165 },
166 // If the transfer fails, return an error.
167 Err(_) => {
168 return Err(EnglishAuctionError::NotSeller(NotSeller{}));
169 }
170
171 }
172 }
173
174 // The bid method allows bidders to place a bid on the auction.
175 #[payable]
176 pub fn bid(&mut self) -> Result<(), EnglishAuctionError> {
177 // Check if the auction has started.
178 if !self.started.get() {
179 // Return an error if the auction has not started.
180 return Err(EnglishAuctionError::NotSeller(NotSeller{}));
181 }
182
183 // Check if the auction has ended.
184 if U256::from(block::timestamp()) >= self.end_at.get() {
185 // Return an error if the auction has ended.
186 return Err(EnglishAuctionError::AuctionEnded(AuctionEnded{}));
187 }
188
189 // Check if the bid amount is higher than the current highest bid.
190 if msg::value() <= self.highest_bid.get() {
191 // Return an error if the bid amount is too low.
192 return Err(EnglishAuctionError::BidTooLow(BidTooLow{}));
193 }
194
195 // Refund the previous highest bidder. (But will not transfer back at this call, needs bidders to call withdraw() to get back the fund.
196 if self.highest_bidder.get() != Address::default() {
197 let mut bid = self.bids.setter(self.highest_bidder.get());
198 let current_bid = bid.get();
199 bid.set(current_bid + self.highest_bid.get());
200 }
201
202 // Update the highest bidder and the highest bid.
203 self.highest_bidder.set(msg::sender());
204 self.highest_bid.set(msg::value());
205
206 // Update the bid of the current bidder.
207 evm::log(Bid {
208 sender: msg::sender(),
209 amount: msg::value(),
210 });
211 Ok(())
212 }
213
214 // The withdraw method allows bidders to withdraw their bid.
215 pub fn withdraw(&mut self) -> Result<(), EnglishAuctionError> {
216 // Get the current bid of the bidder.
217 let mut current_bid = self.bids.setter(msg::sender());
218 let bal = current_bid.get();
219 // Set the record of this bidder to 0 and transfer back tokens.
220 current_bid.set(U256::from(0));
221 let _ = transfer_eth(msg::sender(), bal);
222
223 // Log the withdraw event.
224 evm::log(Withdraw {
225 bidder: msg::sender(),
226 amount: bal,
227 });
228 Ok(())
229 }
230
231 // The end method allows the seller to end the auction.
232 pub fn end<S: TopLevelStorage + BorrowMut<Self>>(storage: &mut S) -> Result<(), EnglishAuctionError> {
233 // Check if the auction has started.
234 if !storage.borrow_mut().started.get() {
235 // Return an error if the auction has not started.
236 return Err(EnglishAuctionError::NotStarted(NotStarted{}));
237 }
238
239 // Check if the auction has ended.
240 if U256::from(block::timestamp()) < storage.borrow_mut().end_at.get() {
241 // Return an error if the auction has not ended.
242 return Err(EnglishAuctionError::NotEnded(NotEnded{}));
243 }
244
245 // Check if the auction has already ended.
246 if storage.borrow_mut().ended.get() {
247 // Return an error if the auction has already ended.
248 return Err(EnglishAuctionError::AuctionEnded(AuctionEnded{}));
249 }
250
251 // End the auction and transfer the NFT and the highest bid to the winner.
252 storage.borrow_mut().ended.set(true);
253 let nft_contract_address = *storage.borrow_mut().nft_address;
254 let seller_address = storage.borrow_mut().seller.get();
255 let highest_bid = storage.borrow_mut().highest_bid.get();
256 let highest_bidder = storage.borrow_mut().highest_bidder.get();
257 let nft_id = storage.borrow_mut().nft_id.get();
258 let config = Call::new_in(storage.borrow_mut());
259
260 let nft = IERC721::new(nft_contract_address);
261
262 // Check if there is highest bidder.
263 if highest_bidder != Address::default() {
264 // If there is a highest bidder, transfer the NFT to the highest bidder.
265 let _ = nft.safe_transfer_from(config, contract::address(), highest_bidder, nft_id);
266 // Transfer the highest bid to the seller.
267 let _ = transfer_eth(seller_address, highest_bid);
268 } else {
269 // If there is no highest bidder, transfer the NFT back to the seller.
270 let _ = nft.safe_transfer_from(config, contract::address(), seller_address, nft_id);
271 }
272
273 // Log the end event.
274 evm::log(End {
275 winner: highest_bidder,
276 amount: highest_bid,
277 });
278 Ok(())
279 }
280}
1// Allow `cargo stylus export-abi` to generate a main function.
2#![cfg_attr(not(feature = "export-abi"), no_main)]
3extern crate alloc;
4
5use std::borrow::BorrowMut;
6
7/// Import items from the SDK. The prelude contains common traits and macros.
8use stylus_sdk::{alloy_primitives::{Address, U256}, block, call::{transfer_eth, Call}, contract, evm, msg, prelude::*};
9use alloy_sol_types::sol;
10
11// Import the IERC721 interface.
12sol_interface! {
13 interface IERC721 {
14 // Required methods.
15 function safeTransferFrom(address from, address to, uint256 token_id) external;
16 function transferFrom(address, address, uint256) external;
17 }
18}
19
20// Define the events and errors for the contract.
21sol!{
22 // Define the events for the contract.
23 event Start(); // Start the auction.
24 event Bid(address indexed sender, uint256 amount); // Bid on the auction.
25 event Withdraw(address indexed bidder, uint256 amount); // Withdraw a bid.
26 event End(address winner, uint256 amount); // End the auction.
27
28 // Define the errors for the contract.
29 error AlreadyInitialized(); // The contract has already been initialized.
30 error AlreadyStarted(); // The auction has already started.
31 error NotSeller(); // The sender is not the seller.
32 error AuctionEnded(); // The auction has ended.
33 error BidTooLow(); // The bid is too low.
34 error NotStarted(); // The auction has not started.
35 error NotEnded(); // The auction has not ended.
36}
37
38
39#[derive(SolidityError)]
40pub enum EnglishAuctionError {
41 // Define the errors for the contract.
42 AlreadyInitialized(AlreadyInitialized),
43 AlreadyStarted(AlreadyStarted),
44 NotSeller(NotSeller),
45 AuctionEnded(AuctionEnded),
46 BidTooLow(BidTooLow),
47 NotStarted(NotStarted),
48 NotEnded(NotEnded),
49}
50
51// Define some persistent storage using the Solidity ABI.
52// `Counter` will be the entrypoint.
53sol_storage! {
54 #[entrypoint]
55 pub struct EnglishAuction {
56 address nft_address; // The address of the NFT contract.
57 uint256 nft_id; // The ID of the NFT.
58
59 address seller; // The address of the seller.
60 uint256 end_at; // The end time of the auction.
61 bool started; // The auction has started or not.
62 bool ended; // The auction has ended or not.
63
64 address highest_bidder; // The address of the highest bidder.
65 uint256 highest_bid; // The highest bid.
66 mapping(address => uint256) bids; // The bids of the bidders.
67 }
68}
69
70/// Declare that `Counter` is a contract with the following external methods.
71#[public]
72impl EnglishAuction {
73 pub const ONE_DAY: u64 = 86400; // 1 day = 24 hours * 60 minutes * 60 seconds = 86400 seconds.
74
75 // Get nft address
76 pub fn nft(&self) -> Result<Address, EnglishAuctionError> {
77 Ok(self.nft_address.get())
78 }
79
80 // Get nft id
81 pub fn nft_id(&self) -> Result<U256, EnglishAuctionError> {
82 Ok(self.nft_id.get())
83 }
84 // Get seller address
85 pub fn seller(&self) -> Result<Address, EnglishAuctionError> {
86 Ok(self.seller.get())
87 }
88
89 // Get end time
90 pub fn end_at(&self) -> Result<U256, EnglishAuctionError> {
91 Ok(self.end_at.get())
92 }
93
94 // Get started status
95 pub fn started(&self) -> Result<bool, EnglishAuctionError> {
96 Ok(self.started.get())
97 }
98
99 // Get ended status
100 pub fn ended(&self) -> Result<bool, EnglishAuctionError> {
101 Ok(self.ended.get())
102 }
103
104 // Get highest bidder address
105 pub fn highest_bidder(&self) -> Result<Address, EnglishAuctionError> {
106 Ok(self.highest_bidder.get())
107 }
108
109 // Get highest bid amount
110 pub fn highest_bid(&self) -> Result<U256, EnglishAuctionError> {
111 Ok(self.highest_bid.get())
112 }
113
114 // Get bid amount of a bidder
115 pub fn bids(&self, bidder: Address) -> Result<U256, EnglishAuctionError> {
116 Ok(self.bids.getter(bidder).get())
117 }
118
119 // Initialize program
120 pub fn initialize(&mut self, nft: Address, nft_id: U256, starting_bid: U256) -> Result<(), EnglishAuctionError> {
121 // Check if the contract has already been initialized.
122 if self.seller.get() != Address::default() {
123 // Return an error if the contract has already been initialized.
124 return Err(EnglishAuctionError::AlreadyInitialized(AlreadyInitialized{}));
125 }
126
127 // Initialize the contract with the NFT address, the NFT ID, the seller, and the starting bid.
128 self.nft_address.set(nft);
129 self.nft_id.set(nft_id);
130 self.seller.set(msg::sender());
131 self.highest_bid.set(starting_bid);
132 Ok(())
133 }
134
135 pub fn start(&mut self) -> Result<(), EnglishAuctionError> {
136 // Check if the auction has already started.
137 if self.started.get() {
138 return Err(EnglishAuctionError::AlreadyStarted(AlreadyStarted{}));
139 }
140
141 // Check if the sender is the seller.
142 if self.seller.get() != msg::sender() {
143 // Return an error if the sender is the seller.
144 return Err(EnglishAuctionError::NotSeller(NotSeller{}));
145 }
146
147 // Create a new instance of the IERC721 interface.
148 let nft = IERC721::new(*self.nft_address);
149 // Get the NFT ID.
150 let nft_id = self.nft_id.get();
151
152 // Transfer the NFT to the contract.
153 let config = Call::new_in(self);
154 let result = nft.transfer_from(config, msg::sender(), contract::address(), nft_id);
155
156 match result {
157 // If the transfer is successful, start the auction.
158 Ok(_) => {
159 self.started.set(true);
160 // Set the end time of the auction to 7 days from now.
161 self.end_at.set(U256::from(block::timestamp() + 7 * Self::ONE_DAY));
162 // Log the start event.
163 evm::log(Start {});
164 Ok(())
165 },
166 // If the transfer fails, return an error.
167 Err(_) => {
168 return Err(EnglishAuctionError::NotSeller(NotSeller{}));
169 }
170
171 }
172 }
173
174 // The bid method allows bidders to place a bid on the auction.
175 #[payable]
176 pub fn bid(&mut self) -> Result<(), EnglishAuctionError> {
177 // Check if the auction has started.
178 if !self.started.get() {
179 // Return an error if the auction has not started.
180 return Err(EnglishAuctionError::NotSeller(NotSeller{}));
181 }
182
183 // Check if the auction has ended.
184 if U256::from(block::timestamp()) >= self.end_at.get() {
185 // Return an error if the auction has ended.
186 return Err(EnglishAuctionError::AuctionEnded(AuctionEnded{}));
187 }
188
189 // Check if the bid amount is higher than the current highest bid.
190 if msg::value() <= self.highest_bid.get() {
191 // Return an error if the bid amount is too low.
192 return Err(EnglishAuctionError::BidTooLow(BidTooLow{}));
193 }
194
195 // Refund the previous highest bidder. (But will not transfer back at this call, needs bidders to call withdraw() to get back the fund.
196 if self.highest_bidder.get() != Address::default() {
197 let mut bid = self.bids.setter(self.highest_bidder.get());
198 let current_bid = bid.get();
199 bid.set(current_bid + self.highest_bid.get());
200 }
201
202 // Update the highest bidder and the highest bid.
203 self.highest_bidder.set(msg::sender());
204 self.highest_bid.set(msg::value());
205
206 // Update the bid of the current bidder.
207 evm::log(Bid {
208 sender: msg::sender(),
209 amount: msg::value(),
210 });
211 Ok(())
212 }
213
214 // The withdraw method allows bidders to withdraw their bid.
215 pub fn withdraw(&mut self) -> Result<(), EnglishAuctionError> {
216 // Get the current bid of the bidder.
217 let mut current_bid = self.bids.setter(msg::sender());
218 let bal = current_bid.get();
219 // Set the record of this bidder to 0 and transfer back tokens.
220 current_bid.set(U256::from(0));
221 let _ = transfer_eth(msg::sender(), bal);
222
223 // Log the withdraw event.
224 evm::log(Withdraw {
225 bidder: msg::sender(),
226 amount: bal,
227 });
228 Ok(())
229 }
230
231 // The end method allows the seller to end the auction.
232 pub fn end<S: TopLevelStorage + BorrowMut<Self>>(storage: &mut S) -> Result<(), EnglishAuctionError> {
233 // Check if the auction has started.
234 if !storage.borrow_mut().started.get() {
235 // Return an error if the auction has not started.
236 return Err(EnglishAuctionError::NotStarted(NotStarted{}));
237 }
238
239 // Check if the auction has ended.
240 if U256::from(block::timestamp()) < storage.borrow_mut().end_at.get() {
241 // Return an error if the auction has not ended.
242 return Err(EnglishAuctionError::NotEnded(NotEnded{}));
243 }
244
245 // Check if the auction has already ended.
246 if storage.borrow_mut().ended.get() {
247 // Return an error if the auction has already ended.
248 return Err(EnglishAuctionError::AuctionEnded(AuctionEnded{}));
249 }
250
251 // End the auction and transfer the NFT and the highest bid to the winner.
252 storage.borrow_mut().ended.set(true);
253 let nft_contract_address = *storage.borrow_mut().nft_address;
254 let seller_address = storage.borrow_mut().seller.get();
255 let highest_bid = storage.borrow_mut().highest_bid.get();
256 let highest_bidder = storage.borrow_mut().highest_bidder.get();
257 let nft_id = storage.borrow_mut().nft_id.get();
258 let config = Call::new_in(storage.borrow_mut());
259
260 let nft = IERC721::new(nft_contract_address);
261
262 // Check if there is highest bidder.
263 if highest_bidder != Address::default() {
264 // If there is a highest bidder, transfer the NFT to the highest bidder.
265 let _ = nft.safe_transfer_from(config, contract::address(), highest_bidder, nft_id);
266 // Transfer the highest bid to the seller.
267 let _ = transfer_eth(seller_address, highest_bid);
268 } else {
269 // If there is no highest bidder, transfer the NFT back to the seller.
270 let _ = nft.safe_transfer_from(config, contract::address(), seller_address, nft_id);
271 }
272
273 // Log the end event.
274 evm::log(End {
275 winner: highest_bidder,
276 amount: highest_bid,
277 });
278 Ok(())
279 }
280}
1[package]
2name = "stylus-auction-example"
3version = "0.1.7"
4edition = "2021"
5license = "MIT OR Apache-2.0"
6keywords = ["arbitrum", "ethereum", "stylus", "alloy"]
7
8[dependencies]
9alloy-primitives = "=0.7.6"
10alloy-sol-types = "=0.7.6"
11mini-alloc = "0.4.2"
12stylus-sdk = "0.6.0"
13hex = "0.4.3"
14
15[dev-dependencies]
16tokio = { version = "1.12.0", features = ["full"] }
17ethers = "2.0"
18eyre = "0.6.8"
19
20[features]
21export-abi = ["stylus-sdk/export-abi"]
22
23[lib]
24crate-type = ["lib", "cdylib"]
25
26[profile.release]
27codegen-units = 1
28strip = true
29lto = true
30panic = "abort"
31opt-level = "s"
1[package]
2name = "stylus-auction-example"
3version = "0.1.7"
4edition = "2021"
5license = "MIT OR Apache-2.0"
6keywords = ["arbitrum", "ethereum", "stylus", "alloy"]
7
8[dependencies]
9alloy-primitives = "=0.7.6"
10alloy-sol-types = "=0.7.6"
11mini-alloc = "0.4.2"
12stylus-sdk = "0.6.0"
13hex = "0.4.3"
14
15[dev-dependencies]
16tokio = { version = "1.12.0", features = ["full"] }
17ethers = "2.0"
18eyre = "0.6.8"
19
20[features]
21export-abi = ["stylus-sdk/export-abi"]
22
23[lib]
24crate-type = ["lib", "cdylib"]
25
26[profile.release]
27codegen-units = 1
28strip = true
29lto = true
30panic = "abort"
31opt-level = "s"