An Arbitrum Stylus version implementation of Solidity Crowd Fund.
This is the logic of the contract:
For Campaign Creators:
For Contributors:
Here is the interface for Crowd Fund.
1// SPDX-License-Identifier: MIT-OR-APACHE-2.0
2pragma solidity ^0.8.23;
3
4interface ICrowdFund {
5 function launch(uint256 goal, uint256 start_at, uint256 end_at) external;
6
7 function cancel(uint256 id) external;
8
9 function pledge(uint256 id, uint256 amount) external;
10
11 function unpledge(uint256 id, uint256 amount) external;
12
13 function claim(uint256 id) external;
14
15 function refund(uint256 id) external;
16}
1// SPDX-License-Identifier: MIT-OR-APACHE-2.0
2pragma solidity ^0.8.23;
3
4interface ICrowdFund {
5 function launch(uint256 goal, uint256 start_at, uint256 end_at) external;
6
7 function cancel(uint256 id) external;
8
9 function pledge(uint256 id, uint256 amount) external;
10
11 function unpledge(uint256 id, uint256 amount) external;
12
13 function claim(uint256 id) external;
14
15 function refund(uint256 id) external;
16}
Example implementation of a Crowd Fund contract written in Rust.
1#![cfg_attr(not(feature = "export-abi"), no_main)]
2extern crate alloc;
3use alloy_sol_types::sol;
4
5use stylus_sdk::{
6 alloy_primitives::{Address, U256},
7 block,
8 call::Call,
9 contract, evm, msg,
10 prelude::*,
11};
12
13sol_interface! {
14 interface IERC20 {
15 function transfer(address, uint256) external returns (bool);
16 function transferFrom(address, address, uint256) external returns (bool);
17 }
18}
19
20sol! {
21 event Launch(
22 uint256 id,
23 address indexed creator,
24 uint256 goal,
25 uint256 start_at,
26 uint256 end_at
27 );
28 event Cancel(uint256 id);
29 event Pledge(uint256 indexed id, address indexed caller, uint256 amount);
30 event Unpledge(uint256 indexed id, address indexed caller, uint256 amount);
31 event Claim(uint256 id);
32 event Refund(uint256 id, address indexed caller, uint256 amount);
33}
34
35sol_storage! {
36 #[entrypoint]
37 pub struct CrowdFund{
38 // Total count of campaigns created.
39 // It is also used to generate id for new campaigns.
40 uint256 count;
41 // The address of the NFT contract.
42 address token_address;
43 // Mapping from id to Campaign
44 CampaignStruct[] campaigns; // The transactions array
45 // Mapping from campaign id => pledger => amount pledged
46 mapping(uint256 => mapping(address => uint256)) pledged_amount;
47 }
48 pub struct CampaignStruct {
49 // Creator of campaign
50 address creator;
51 // Amount of tokens to raise
52 uint256 goal;
53 // Total amount pledged
54 uint256 pledged;
55 // Timestamp of start of campaign
56 uint256 start_at;
57 // Timestamp of end of campaign
58 uint256 end_at;
59 // True if goal was reached and creator has claimed the tokens.
60 bool claimed;
61 }
62
63}
64
65/// Declare that `CrowdFund` is a contract with the following external methods.
66#[public]
67impl CrowdFund {
68 pub const ONE_DAY: u64 = 86400; // 1 day = 24 hours * 60 minutes * 60 seconds = 86400 seconds.
69
70 pub fn launch(&mut self, goal: U256, start_at: U256, end_at: U256) {
71 assert!(start_at < U256::from(block::timestamp()));
72 assert!(end_at < start_at);
73 assert!(end_at > U256::from(block::timestamp() + 7 * Self::ONE_DAY));
74
75 let number = self.count.get();
76 self.count.set(number + U256::from(1));
77
78 let mut new_campaign = self.campaigns.grow();
79 new_campaign.creator.set(msg::sender());
80 new_campaign.goal.set(goal);
81 new_campaign.pledged.set(U256::from(0));
82 new_campaign.start_at.set(start_at);
83 new_campaign.end_at.set(end_at);
84 new_campaign.claimed.set(false);
85 let number = U256::from(self.campaigns.len());
86 evm::log(Launch {
87 id: number - U256::from(1),
88 creator: msg::sender(),
89 goal: goal,
90 start_at: start_at,
91 end_at: end_at,
92 });
93 }
94 pub fn cancel(&mut self, id: U256) {
95 if let Some(mut entry) = self.campaigns.get_mut(id) {
96 if entry.creator.get() == msg::sender()
97 && U256::from(block::timestamp()) > entry.start_at.get()
98 {
99 entry.creator.set(Address::ZERO);
100 entry.goal.set(U256::from(0));
101 entry.pledged.set(U256::from(0));
102 entry.start_at.set(U256::from(0));
103 entry.end_at.set(U256::from(0));
104 entry.claimed.set(false);
105 evm::log(Cancel { id: id });
106 }
107 }
108 }
109 pub fn pledge(&mut self, id: U256, amount: U256) {
110 if let Some(mut entry) = self.campaigns.get_mut(id) {
111 if U256::from(block::timestamp()) >= entry.start_at.get()
112 && U256::from(block::timestamp()) <= entry.end_at.get()
113 {
114 let pledged = U256::from(entry.pledged.get());
115 entry.pledged.set(pledged + amount);
116 let mut pledged_amount_info = self.pledged_amount.setter(id);
117 let mut pledged_amount_sender = pledged_amount_info.setter(msg::sender());
118 let old_amount = pledged_amount_sender.get();
119 pledged_amount_sender.set(old_amount + amount);
120
121 let token = IERC20::new(*self.token_address);
122 let config = Call::new_in(self);
123 token.transfer(config, contract::address(), amount).unwrap();
124 }
125 }
126 }
127 pub fn unpledge(&mut self, id: U256, amount: U256) {
128 if let Some(mut entry) = self.campaigns.get_mut(id) {
129 if U256::from(block::timestamp()) <= entry.end_at.get() {
130 let pledged = U256::from(entry.pledged.get());
131 entry.pledged.set(pledged - amount);
132 let mut pledged_amount_info = self.pledged_amount.setter(id);
133 let mut pledged_amount_sender = pledged_amount_info.setter(msg::sender());
134 let old_amount = pledged_amount_sender.get();
135 pledged_amount_sender.set(old_amount - amount);
136 // Token transfer
137 let token = IERC20::new(*self.token_address);
138 let config = Call::new_in(self);
139 token.transfer(config, contract::address(), amount).unwrap();
140 // Emit the log
141 evm::log(Unpledge {
142 id: id,
143 caller: msg::sender(),
144 amount: amount,
145 });
146 }
147 }
148 }
149 pub fn claim(&mut self, id: U256) {
150 // First mutable borrow to access campaigns and the entry
151 if let Some(mut entry) = self.campaigns.get_mut(id) {
152 let creator = entry.creator.get();
153 let end_at = entry.end_at.get();
154 let pledged = entry.pledged.get();
155 let goal = entry.goal.get();
156 let claimed = entry.claimed.get();
157
158 // Check conditions on the entry
159 if creator == msg::sender()
160 && U256::from(block::timestamp()) > end_at
161 && pledged >= goal
162 && !claimed
163 {
164 // Mark the entry as claimed
165 entry.claimed.set(true);
166
167 // Now, perform the token transfer
168 let token_address = *self.token_address;
169 let token = IERC20::new(token_address);
170
171 let config = Call::new_in(self);
172 token.transfer(config, creator, pledged).unwrap();
173 evm::log(Claim { id: id });
174 }
175 }
176 }
177
178 pub fn refund(&mut self, id: U256) {
179 // First mutable borrow to access campaigns and the entry
180 if let Some(entry) = self.campaigns.get_mut(id) {
181 let end_at = entry.end_at.get();
182 let goal = entry.goal.get();
183 let pledged = entry.pledged.get();
184
185 if U256::from(block::timestamp()) > end_at && pledged < goal {
186 let mut pledged_amount_info = self.pledged_amount.setter(id);
187 let mut pledged_amount_sender = pledged_amount_info.setter(msg::sender());
188 let old_balance = pledged_amount_sender.get();
189 pledged_amount_sender.set(U256::from(0));
190 let token_address = *self.token_address;
191 let token = IERC20::new(token_address);
192
193 let config = Call::new_in(self);
194 token.transfer(config, msg::sender(), old_balance).unwrap();
195 evm::log(Refund {
196 id: id,
197 caller: msg::sender(),
198 amount: old_balance,
199 });
200 }
201 }
202 }
203}
1#![cfg_attr(not(feature = "export-abi"), no_main)]
2extern crate alloc;
3use alloy_sol_types::sol;
4
5use stylus_sdk::{
6 alloy_primitives::{Address, U256},
7 block,
8 call::Call,
9 contract, evm, msg,
10 prelude::*,
11};
12
13sol_interface! {
14 interface IERC20 {
15 function transfer(address, uint256) external returns (bool);
16 function transferFrom(address, address, uint256) external returns (bool);
17 }
18}
19
20sol! {
21 event Launch(
22 uint256 id,
23 address indexed creator,
24 uint256 goal,
25 uint256 start_at,
26 uint256 end_at
27 );
28 event Cancel(uint256 id);
29 event Pledge(uint256 indexed id, address indexed caller, uint256 amount);
30 event Unpledge(uint256 indexed id, address indexed caller, uint256 amount);
31 event Claim(uint256 id);
32 event Refund(uint256 id, address indexed caller, uint256 amount);
33}
34
35sol_storage! {
36 #[entrypoint]
37 pub struct CrowdFund{
38 // Total count of campaigns created.
39 // It is also used to generate id for new campaigns.
40 uint256 count;
41 // The address of the NFT contract.
42 address token_address;
43 // Mapping from id to Campaign
44 CampaignStruct[] campaigns; // The transactions array
45 // Mapping from campaign id => pledger => amount pledged
46 mapping(uint256 => mapping(address => uint256)) pledged_amount;
47 }
48 pub struct CampaignStruct {
49 // Creator of campaign
50 address creator;
51 // Amount of tokens to raise
52 uint256 goal;
53 // Total amount pledged
54 uint256 pledged;
55 // Timestamp of start of campaign
56 uint256 start_at;
57 // Timestamp of end of campaign
58 uint256 end_at;
59 // True if goal was reached and creator has claimed the tokens.
60 bool claimed;
61 }
62
63}
64
65/// Declare that `CrowdFund` is a contract with the following external methods.
66#[public]
67impl CrowdFund {
68 pub const ONE_DAY: u64 = 86400; // 1 day = 24 hours * 60 minutes * 60 seconds = 86400 seconds.
69
70 pub fn launch(&mut self, goal: U256, start_at: U256, end_at: U256) {
71 assert!(start_at < U256::from(block::timestamp()));
72 assert!(end_at < start_at);
73 assert!(end_at > U256::from(block::timestamp() + 7 * Self::ONE_DAY));
74
75 let number = self.count.get();
76 self.count.set(number + U256::from(1));
77
78 let mut new_campaign = self.campaigns.grow();
79 new_campaign.creator.set(msg::sender());
80 new_campaign.goal.set(goal);
81 new_campaign.pledged.set(U256::from(0));
82 new_campaign.start_at.set(start_at);
83 new_campaign.end_at.set(end_at);
84 new_campaign.claimed.set(false);
85 let number = U256::from(self.campaigns.len());
86 evm::log(Launch {
87 id: number - U256::from(1),
88 creator: msg::sender(),
89 goal: goal,
90 start_at: start_at,
91 end_at: end_at,
92 });
93 }
94 pub fn cancel(&mut self, id: U256) {
95 if let Some(mut entry) = self.campaigns.get_mut(id) {
96 if entry.creator.get() == msg::sender()
97 && U256::from(block::timestamp()) > entry.start_at.get()
98 {
99 entry.creator.set(Address::ZERO);
100 entry.goal.set(U256::from(0));
101 entry.pledged.set(U256::from(0));
102 entry.start_at.set(U256::from(0));
103 entry.end_at.set(U256::from(0));
104 entry.claimed.set(false);
105 evm::log(Cancel { id: id });
106 }
107 }
108 }
109 pub fn pledge(&mut self, id: U256, amount: U256) {
110 if let Some(mut entry) = self.campaigns.get_mut(id) {
111 if U256::from(block::timestamp()) >= entry.start_at.get()
112 && U256::from(block::timestamp()) <= entry.end_at.get()
113 {
114 let pledged = U256::from(entry.pledged.get());
115 entry.pledged.set(pledged + amount);
116 let mut pledged_amount_info = self.pledged_amount.setter(id);
117 let mut pledged_amount_sender = pledged_amount_info.setter(msg::sender());
118 let old_amount = pledged_amount_sender.get();
119 pledged_amount_sender.set(old_amount + amount);
120
121 let token = IERC20::new(*self.token_address);
122 let config = Call::new_in(self);
123 token.transfer(config, contract::address(), amount).unwrap();
124 }
125 }
126 }
127 pub fn unpledge(&mut self, id: U256, amount: U256) {
128 if let Some(mut entry) = self.campaigns.get_mut(id) {
129 if U256::from(block::timestamp()) <= entry.end_at.get() {
130 let pledged = U256::from(entry.pledged.get());
131 entry.pledged.set(pledged - amount);
132 let mut pledged_amount_info = self.pledged_amount.setter(id);
133 let mut pledged_amount_sender = pledged_amount_info.setter(msg::sender());
134 let old_amount = pledged_amount_sender.get();
135 pledged_amount_sender.set(old_amount - amount);
136 // Token transfer
137 let token = IERC20::new(*self.token_address);
138 let config = Call::new_in(self);
139 token.transfer(config, contract::address(), amount).unwrap();
140 // Emit the log
141 evm::log(Unpledge {
142 id: id,
143 caller: msg::sender(),
144 amount: amount,
145 });
146 }
147 }
148 }
149 pub fn claim(&mut self, id: U256) {
150 // First mutable borrow to access campaigns and the entry
151 if let Some(mut entry) = self.campaigns.get_mut(id) {
152 let creator = entry.creator.get();
153 let end_at = entry.end_at.get();
154 let pledged = entry.pledged.get();
155 let goal = entry.goal.get();
156 let claimed = entry.claimed.get();
157
158 // Check conditions on the entry
159 if creator == msg::sender()
160 && U256::from(block::timestamp()) > end_at
161 && pledged >= goal
162 && !claimed
163 {
164 // Mark the entry as claimed
165 entry.claimed.set(true);
166
167 // Now, perform the token transfer
168 let token_address = *self.token_address;
169 let token = IERC20::new(token_address);
170
171 let config = Call::new_in(self);
172 token.transfer(config, creator, pledged).unwrap();
173 evm::log(Claim { id: id });
174 }
175 }
176 }
177
178 pub fn refund(&mut self, id: U256) {
179 // First mutable borrow to access campaigns and the entry
180 if let Some(entry) = self.campaigns.get_mut(id) {
181 let end_at = entry.end_at.get();
182 let goal = entry.goal.get();
183 let pledged = entry.pledged.get();
184
185 if U256::from(block::timestamp()) > end_at && pledged < goal {
186 let mut pledged_amount_info = self.pledged_amount.setter(id);
187 let mut pledged_amount_sender = pledged_amount_info.setter(msg::sender());
188 let old_balance = pledged_amount_sender.get();
189 pledged_amount_sender.set(U256::from(0));
190 let token_address = *self.token_address;
191 let token = IERC20::new(token_address);
192
193 let config = Call::new_in(self);
194 token.transfer(config, msg::sender(), old_balance).unwrap();
195 evm::log(Refund {
196 id: id,
197 caller: msg::sender(),
198 amount: old_balance,
199 });
200 }
201 }
202 }
203}
1[package]
2name = "stylus-crowd-fund"
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"
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-crowd-fund"
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"
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"