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