Skip to content

Commit f0eea1b

Browse files
committed
feat(contract): implement add_supported_wrapped_token and remove_wrap… (Fracverse#1003)
1 parent c96b735 commit f0eea1b

860 files changed

Lines changed: 238260 additions & 26145 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.gitignore‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,6 @@ yarn-error.log*
5151
# typescript
5252
*.tsbuildinfo
5353
next-env.d.ts
54+
branch_structure.json
55+
temp_auto_push.bat
56+
temp_interactive_push.bat

‎contracts/Cargo.lock‎

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎contracts/Cargo.toml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
[workspace]
22
resolver = "2"
33
members = [
4+
"access-control",
45
"inheritance-contract",
6+
"lending-contract",
7+
"loan-nft",
58
"mock-token",
69
]
710

11+
12+
813
[workspace.dependencies]
914
soroban-sdk = "21.7.7"
1015
rand_core = "0.8"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "access-control"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
crate-type = ["lib"]
8+
9+
[dependencies]
10+
soroban-sdk = { workspace = true }
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
#![no_std]
2+
3+
use soroban_sdk::{contracttype, Address, Env, Vec};
4+
5+
/// The four roles recognised across all InheritX contracts.
6+
#[contracttype]
7+
#[derive(Clone, Debug, Eq, PartialEq)]
8+
pub enum Role {
9+
Admin,
10+
Guardian,
11+
Beneficiary,
12+
Owner,
13+
}
14+
15+
/// Per-address storage key for role lists.
16+
#[contracttype]
17+
#[derive(Clone)]
18+
pub enum AccessControlKey {
19+
Roles(Address),
20+
}
21+
22+
/// Assign `role` to `address`. Idempotent — does nothing if already assigned.
23+
pub fn assign_role(env: &Env, address: &Address, role: Role) {
24+
let key = AccessControlKey::Roles(address.clone());
25+
let mut roles: Vec<Role> = env
26+
.storage()
27+
.persistent()
28+
.get(&key)
29+
.unwrap_or(Vec::new(env));
30+
for existing in roles.iter() {
31+
if existing == role {
32+
return;
33+
}
34+
}
35+
roles.push_back(role);
36+
env.storage().persistent().set(&key, &roles);
37+
}
38+
39+
/// Revoke `role` from `address`. Idempotent — does nothing if not assigned.
40+
pub fn revoke_role(env: &Env, address: &Address, role: Role) {
41+
reentrancy_enter_or_panic(env);
42+
let key = AccessControlKey::Roles(address.clone());
43+
let roles: Vec<Role> = env
44+
.storage()
45+
.persistent()
46+
.get(&key)
47+
.unwrap_or(Vec::new(env));
48+
let mut updated = Vec::new(env);
49+
for existing in roles.iter() {
50+
if existing != role {
51+
updated.push_back(existing);
52+
}
53+
}
54+
env.storage().persistent().set(&key, &updated);
55+
reentrancy_exit(env);
56+
}
57+
58+
/// Return `true` if `address` currently holds `role`.
59+
pub fn has_role(env: &Env, address: &Address, role: Role) -> bool {
60+
let key = AccessControlKey::Roles(address.clone());
61+
let roles: Vec<Role> = env
62+
.storage()
63+
.persistent()
64+
.get(&key)
65+
.unwrap_or(Vec::new(env));
66+
for existing in roles.iter() {
67+
if existing == role {
68+
return true;
69+
}
70+
}
71+
false
72+
}
73+
74+
/// Require that `address` holds `role`; panics with `contract_error` otherwise.
75+
///
76+
/// Pattern: `require_role(env, &caller, Role::Admin, ContractError::AccessDenied)?;`
77+
pub fn require_role<E: Into<soroban_sdk::Error> + Copy>(
78+
env: &Env,
79+
address: &Address,
80+
role: Role,
81+
contract_error: E,
82+
) -> Result<(), E> {
83+
if has_role(env, address, role) {
84+
Ok(())
85+
} else {
86+
Err(contract_error)
87+
}
88+
}
89+
90+
// ─── Reentrancy Guard ────────────────────────────
91+
92+
#[contracttype]
93+
#[derive(Clone)]
94+
pub enum SecurityKey {
95+
ReentrancyLock,
96+
}
97+
98+
/// Enter the reentrancy guard. Returns `error` if a reentrant call is detected.
99+
pub fn reentrancy_enter<E: Into<soroban_sdk::Error> + Copy>(env: &Env, error: E) -> Result<(), E> {
100+
if env.storage().instance().has(&SecurityKey::ReentrancyLock) {
101+
return Err(error);
102+
}
103+
env.storage()
104+
.instance()
105+
.set(&SecurityKey::ReentrancyLock, &true);
106+
Ok(())
107+
}
108+
109+
/// Enter the reentrancy guard. Panics if a reentrant call is detected.
110+
/// Use this for contracts whose error enum is full (e.g. InheritanceContract).
111+
pub fn reentrancy_enter_or_panic(env: &Env) {
112+
if env.storage().instance().has(&SecurityKey::ReentrancyLock) {
113+
panic!("reentrant call");
114+
}
115+
env.storage()
116+
.instance()
117+
.set(&SecurityKey::ReentrancyLock, &true);
118+
}
119+
120+
/// Release the reentrancy guard. Always call this before returning.
121+
/// Safe to skip on panic — Soroban reverts all storage on trap.
122+
pub fn reentrancy_exit(env: &Env) {
123+
env.storage()
124+
.instance()
125+
.remove(&SecurityKey::ReentrancyLock);
126+
}
127+
128+
// ─── Pause / Circuit Breaker ─────────────────────
129+
130+
#[contracttype]
131+
#[derive(Clone)]
132+
pub enum PauseKey {
133+
Paused,
134+
/// Temporary lock set while a pause/unpause operation is in progress.
135+
PauseLock,
136+
/// Count of active operations that have entered; used to prevent pausing
137+
/// while operations are running.
138+
ActiveOps,
139+
}
140+
141+
/// Mark the contract as paused.
142+
pub fn pause_contract(env: &Env) {
143+
// Prevent new operations from starting while we attempt to pause.
144+
env.storage().instance().set(&PauseKey::PauseLock, &true);
145+
// If there are active operations, abort and release the lock.
146+
let active: i128 = env
147+
.storage()
148+
.instance()
149+
.get::<PauseKey, i128>(&PauseKey::ActiveOps)
150+
.unwrap_or(0);
151+
if active != 0 {
152+
env.storage().instance().remove(&PauseKey::PauseLock);
153+
panic!("cannot pause: active operations present");
154+
}
155+
env.storage().instance().set(&PauseKey::Paused, &true);
156+
env.storage().instance().remove(&PauseKey::PauseLock);
157+
}
158+
159+
/// Mark the contract as unpaused.
160+
pub fn unpause_contract(env: &Env) {
161+
// Prevent new operations from starting while we change pause state.
162+
env.storage().instance().set(&PauseKey::PauseLock, &true);
163+
env.storage().instance().set(&PauseKey::Paused, &false);
164+
env.storage().instance().remove(&PauseKey::PauseLock);
165+
}
166+
167+
/// Returns true if the contract is currently paused.
168+
pub fn is_contract_paused(env: &Env) -> bool {
169+
env.storage()
170+
.instance()
171+
.get::<PauseKey, bool>(&PauseKey::Paused)
172+
.unwrap_or(false)
173+
}
174+
175+
/// Fail with `error` if the contract is paused.
176+
pub fn require_not_paused<E: Into<soroban_sdk::Error> + Copy>(
177+
env: &Env,
178+
error: E,
179+
) -> Result<(), E> {
180+
// Treat an in-progress pause/unpause (PauseLock) as paused for operation
181+
// validation so operation start is atomic with pause state changes.
182+
let pause_lock: bool = env
183+
.storage()
184+
.instance()
185+
.get::<PauseKey, bool>(&PauseKey::PauseLock)
186+
.unwrap_or(false);
187+
if is_contract_paused(env) || pause_lock {
188+
return Err(error);
189+
}
190+
Ok(())
191+
}
192+
193+
/// Panic if the contract is paused.
194+
/// Use this for contracts whose error enum is full.
195+
pub fn require_not_paused_or_panic(env: &Env) {
196+
let pause_lock: bool = env
197+
.storage()
198+
.instance()
199+
.get::<PauseKey, bool>(&PauseKey::PauseLock)
200+
.unwrap_or(false);
201+
if is_contract_paused(env) || pause_lock {
202+
panic!("contract paused");
203+
}
204+
}
205+
206+
/// Operation enter/exit helpers to make pause/unpause atomic with operation
207+
/// validation. Call `operation_enter_or_panic` at the start of an operation and
208+
/// `operation_exit` at the end (use `reentrancy_enter`/`reentrancy_exit` as
209+
/// needed for reentrancy protection). These ensure pause operations cannot
210+
/// start while a pause/unpause is in progress and that pausing will fail if
211+
/// active operations exist.
212+
pub fn operation_enter_or_panic(env: &Env) {
213+
// Do not allow starting an operation while a pause/unpause is in progress.
214+
let pause_lock: bool = env
215+
.storage()
216+
.instance()
217+
.get::<PauseKey, bool>(&PauseKey::PauseLock)
218+
.unwrap_or(false);
219+
if pause_lock {
220+
panic!("pause in progress");
221+
}
222+
if is_contract_paused(env) {
223+
panic!("contract paused");
224+
}
225+
let cnt: i128 = env
226+
.storage()
227+
.instance()
228+
.get::<PauseKey, i128>(&PauseKey::ActiveOps)
229+
.unwrap_or(0);
230+
env.storage()
231+
.instance()
232+
.set(&PauseKey::ActiveOps, &(cnt + 1));
233+
}
234+
235+
/// Decrement active operation count. Safe to call even if count is missing.
236+
pub fn operation_exit(env: &Env) {
237+
let cnt: i128 = env
238+
.storage()
239+
.instance()
240+
.get::<PauseKey, i128>(&PauseKey::ActiveOps)
241+
.unwrap_or(0);
242+
if cnt <= 1 {
243+
env.storage().instance().remove(&PauseKey::ActiveOps);
244+
} else {
245+
env.storage()
246+
.instance()
247+
.set(&PauseKey::ActiveOps, &(cnt - 1));
248+
}
249+
}
250+
251+
// ─── Version Compatibility ───────────────────────
252+
253+
#[contracttype]
254+
#[derive(Clone)]
255+
pub enum VersionKey {
256+
ContractVersion,
257+
}
258+
259+
/// Store the contract version in storage. Call this during contract initialization.
260+
pub fn set_contract_version(env: &Env, version: u32) {
261+
env.storage()
262+
.instance()
263+
.set(&VersionKey::ContractVersion, &version);
264+
}
265+
266+
/// Retrieve the contract version from storage.
267+
pub fn get_contract_version(env: &Env) -> u32 {
268+
env.storage()
269+
.instance()
270+
.get(&VersionKey::ContractVersion)
271+
.unwrap_or(1)
272+
}
273+
274+
/// Verify that a cross-contract call target has a compatible version.
275+
/// Returns `error` if the target contract version is outside the acceptable range.
276+
pub fn check_contract_version<E: Into<soroban_sdk::Error> + Copy>(
277+
_env: &Env,
278+
_target_contract: &Address,
279+
_min_version: u32,
280+
_max_version: u32,
281+
_error: E,
282+
) -> Result<(), E> {
283+
// For now, skip version checking to avoid compilation issues
284+
// TODO: Implement proper cross-contract version checking
285+
Ok(())
286+
}

‎contracts/inheritance-contract/Cargo.toml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ crate-type = ["cdylib"]
99
doctest = false
1010

1111
[dependencies]
12+
access-control = { path = "../access-control" }
1213
mock_token = { path = "../mock-token" }
1314
soroban-sdk = { workspace = true }
1415

16+
1517
[dev-dependencies]
1618
soroban-sdk = { workspace = true, features = ["testutils"] }

0 commit comments

Comments
 (0)