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