Skip to content

Commit 96831eb

Browse files
Merge branch 'dev' into fix/pools
2 parents a900a43 + 706c03e commit 96831eb

35 files changed

Lines changed: 1607 additions & 22 deletions

File tree

.cursor/debug-3cec5e.log

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{"sessionId":"3cec5e","location":"route.ts:POST","message":"Admin keypair info","data":{"adminPublicKey":"GDEQD7CITHS4AINJTA4VSACHOXK6ZOY6WTFUNLRHXTCLZXZ5TI4Y7Y5X","requestedAddress":"GA6Q2HIOUFZHNQ4RNL4INHZ7QPJ7RSB5YOIBRZEYHUJNKNL5FBE4M3C6"},"timestamp":1772938297455,"hypothesisId":"H1"}
2+
{"sessionId":"3cec5e","location":"route.ts:path-check","message":"Which mint path","data":{"faucetContractId":"NOT_SET","usingBulk":false},"timestamp":1772938297456,"hypothesisId":"H3"}
3+
{"sessionId":"3cec5e","location":"route.ts:legacy-mint-start","message":"Minting token (legacy)","data":{"symbol":"USTRY","contractId":"CC6SODKGOTFEDWVNPR6ESJC3GL7NC5Y4DVFKYGATZJ74F2YXHTW4RJ6D","mintAmount":"1000000000","adminPubKey":"GDEQD7CITHS4AINJTA4VSACHOXK6ZOY6WTFUNLRHXTCLZXZ5TI4Y7Y5X"},"timestamp":1772938297456,"hypothesisId":"H2"}
4+
{"sessionId":"3cec5e","location":"route.ts:legacy-mint-ok","message":"Token minted OK","data":{"symbol":"USTRY","hash":"f8124f7df212c8080e3a0d42a45cd1a3fd9345ce9f86b346634d0aba224a6885"},"timestamp":1772938300940,"hypothesisId":"H2"}
5+
{"sessionId":"3cec5e","location":"route.ts:legacy-mint-start","message":"Minting token (legacy)","data":{"symbol":"TESOURO","contractId":"CA55OO3U556GXABJKDYP3QCGZ6AFNZPB27TROYP42AQPFFYPKU5EDOUH","mintAmount":"1000000000","adminPubKey":"GDEQD7CITHS4AINJTA4VSACHOXK6ZOY6WTFUNLRHXTCLZXZ5TI4Y7Y5X"},"timestamp":1772938300943,"hypothesisId":"H2"}
6+
{"sessionId":"3cec5e","location":"route.ts:legacy-mint-ok","message":"Token minted OK","data":{"symbol":"TESOURO","hash":"c9ad99e25fe023d1a143add23780edc3e94a9a8337947156d2726bea6f1e23b3"},"timestamp":1772938306315,"hypothesisId":"H2"}
7+
{"sessionId":"3cec5e","location":"route.ts:legacy-mint-start","message":"Minting token (legacy)","data":{"symbol":"CETES","contractId":"CCGWKS4GLAGPYIAOLBH6JM5RKUPMUCN47VRAEXAJWJFKXSXQ33VIRUAA","mintAmount":"1000000000","adminPubKey":"GDEQD7CITHS4AINJTA4VSACHOXK6ZOY6WTFUNLRHXTCLZXZ5TI4Y7Y5X"},"timestamp":1772938306317,"hypothesisId":"H2"}
8+
{"sessionId":"3cec5e","location":"route.ts:legacy-mint-ok","message":"Token minted OK","data":{"symbol":"CETES","hash":"6c916e63c42a96acb3a2fef48b8f801d67197e9cc661dc6d50083fef871f78fe"},"timestamp":1772938312060,"hypothesisId":"H2"}
9+
{"sessionId":"3cec5e","location":"route.ts:legacy-mint-start","message":"Minting token (legacy)","data":{"symbol":"USDY","contractId":"CBVLFSVBZGHVAH6CV4JQYPBJSX75VFR2NJC7CQX7QKQ7KOLGQZOZAGQK","mintAmount":"1000000000","adminPubKey":"GDEQD7CITHS4AINJTA4VSACHOXK6ZOY6WTFUNLRHXTCLZXZ5TI4Y7Y5X"},"timestamp":1772938312063,"hypothesisId":"H2"}
10+
{"sessionId":"3cec5e","location":"route.ts:legacy-mint-ok","message":"Token minted OK","data":{"symbol":"USDY","hash":"954b97f783d3f187f9f8f6c823559a16ba317e2883b16471b29923ee852793d5"},"timestamp":1772938316365,"hypothesisId":"H2"}
11+
{"sessionId":"3cec5e","location":"route.ts:legacy-mint-start","message":"Minting token (legacy)","data":{"symbol":"PYUSD","contractId":"CBCB5UDYZENTIUVVA7SHQOCVVCDXDMHEKJHHFM3OQKU2E5CAF2B62TIO","mintAmount":"1000000000","adminPubKey":"GDEQD7CITHS4AINJTA4VSACHOXK6ZOY6WTFUNLRHXTCLZXZ5TI4Y7Y5X"},"timestamp":1772938316367,"hypothesisId":"H2"}
12+
{"sessionId":"3cec5e","location":"route.ts:legacy-mint-ok","message":"Token minted OK","data":{"symbol":"PYUSD","hash":"2e05db887e2ff78b43d0b7c40ef3d79e51cedaeb3a3bd9df79664779b79fbd31"},"timestamp":1772938321012,"hypothesisId":"H2"}

apps/contracts/stellar-contracts/Cargo.lock

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

apps/contracts/stellar-contracts/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ members = [
55
"rwa-lending",
66
"rwa-oracle",
77
"rwa-token",
8-
"rwa-perps"
8+
"rwa-perps",
9+
"rwa-faucet"
910
]
1011

1112
[workspace.package]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "rwa-faucet"
3+
description = "RWA Faucet contract - Bulk mints multiple RWA tokens in a single Soroban invocation"
4+
version.workspace = true
5+
authors.workspace = true
6+
license.workspace = true
7+
edition.workspace = true
8+
repository.workspace = true
9+
publish = false
10+
11+
[lib]
12+
crate-type = ["cdylib", "rlib"]
13+
doctest = false
14+
15+
[dependencies]
16+
soroban-sdk = { workspace = true }
17+
18+
[dev-dependencies]
19+
soroban-sdk = { workspace = true, features = ["testutils"] }
20+
21+
[package.metadata.stellar]
22+
contract = true
23+
cargo_inherit = true
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use soroban_sdk::{contract, contractimpl, vec, Address, Env, IntoVal, Symbol, Vec};
2+
3+
use crate::storage::Storage;
4+
use crate::types::MintRequest;
5+
6+
#[contract]
7+
pub struct Faucet;
8+
9+
#[contractimpl]
10+
impl Faucet {
11+
/// Initialize the faucet with an admin address.
12+
/// The admin must be the same account that controls the rwa-token contracts.
13+
pub fn initialize(env: Env, admin: Address) {
14+
assert!(!Storage::is_initialized(&env), "Faucet: already initialized");
15+
admin.require_auth();
16+
Storage::set_admin(&env, &admin);
17+
Storage::set_initialized(&env);
18+
}
19+
20+
/// Mint multiple tokens in a single invocation (permissionless on testnet).
21+
/// The faucet contract must be the admin of each token contract so that
22+
/// cross-contract calls to set_authorized and mint are auto-authorized.
23+
pub fn bulk_mint(env: Env, requests: Vec<MintRequest>) {
24+
for req in requests.iter() {
25+
env.invoke_contract::<()>(
26+
&req.token,
27+
&Symbol::new(&env, "set_authorized"),
28+
vec![&env, req.to.into_val(&env), true.into_val(&env)],
29+
);
30+
env.invoke_contract::<()>(
31+
&req.token,
32+
&Symbol::new(&env, "mint"),
33+
vec![&env, req.to.into_val(&env), req.amount.into_val(&env)],
34+
);
35+
}
36+
}
37+
38+
/// Return the admin address.
39+
pub fn admin(env: Env) -> Address {
40+
Storage::get_admin(&env)
41+
}
42+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#![no_std]
2+
3+
pub mod contract;
4+
pub mod storage;
5+
pub mod types;
6+
7+
#[cfg(test)]
8+
mod test;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use soroban_sdk::{contracttype, Address, Env};
2+
3+
#[contracttype]
4+
enum DataKey {
5+
Admin,
6+
Initialized,
7+
}
8+
9+
pub struct Storage;
10+
11+
impl Storage {
12+
pub fn set_admin(env: &Env, admin: &Address) {
13+
env.storage().instance().set(&DataKey::Admin, admin);
14+
}
15+
16+
pub fn get_admin(env: &Env) -> Address {
17+
env.storage()
18+
.instance()
19+
.get(&DataKey::Admin)
20+
.expect("Faucet: admin not set")
21+
}
22+
23+
pub fn set_initialized(env: &Env) {
24+
env.storage().instance().set(&DataKey::Initialized, &true);
25+
}
26+
27+
pub fn is_initialized(env: &Env) -> bool {
28+
env.storage()
29+
.instance()
30+
.get(&DataKey::Initialized)
31+
.unwrap_or(false)
32+
}
33+
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
#![cfg(test)]
2+
extern crate std;
3+
4+
use crate::contract::{Faucet, FaucetClient};
5+
use crate::types::MintRequest;
6+
use soroban_sdk::{
7+
testutils::Address as _,
8+
token::TokenClient,
9+
Address, Env, Vec,
10+
};
11+
12+
/// Use Stellar Asset Contract as the mintable token for testing.
13+
/// The issuer acts as the admin who can mint.
14+
fn create_test_token(env: &Env, admin: &Address) -> Address {
15+
let contract = env.register_stellar_asset_contract_v2(admin.clone());
16+
contract.address()
17+
}
18+
19+
fn create_faucet<'a>(env: &Env) -> (FaucetClient<'a>, Address) {
20+
let address = env.register(Faucet, ());
21+
let client = FaucetClient::new(env, &address);
22+
(client, address)
23+
}
24+
25+
#[test]
26+
fn test_initialize() {
27+
let env = Env::default();
28+
env.mock_all_auths();
29+
30+
let admin = Address::generate(&env);
31+
let (faucet, _) = create_faucet(&env);
32+
33+
faucet.initialize(&admin);
34+
assert_eq!(faucet.admin(), admin);
35+
}
36+
37+
#[test]
38+
#[should_panic(expected = "Faucet: already initialized")]
39+
fn test_initialize_twice_panics() {
40+
let env = Env::default();
41+
env.mock_all_auths();
42+
43+
let admin = Address::generate(&env);
44+
let (faucet, _) = create_faucet(&env);
45+
46+
faucet.initialize(&admin);
47+
faucet.initialize(&admin);
48+
}
49+
50+
#[test]
51+
fn test_bulk_mint_single_token() {
52+
let env = Env::default();
53+
env.mock_all_auths();
54+
55+
let admin = Address::generate(&env);
56+
let user = Address::generate(&env);
57+
58+
let token_addr = create_test_token(&env, &admin);
59+
let token_client = TokenClient::new(&env, &token_addr);
60+
61+
let (faucet, _) = create_faucet(&env);
62+
faucet.initialize(&admin);
63+
64+
let requests = Vec::from_array(
65+
&env,
66+
[MintRequest {
67+
token: token_addr.clone(),
68+
to: user.clone(),
69+
amount: 1_000_0000000, // 1000 with 7 decimals
70+
}],
71+
);
72+
73+
faucet.bulk_mint(&requests);
74+
75+
assert_eq!(token_client.balance(&user), 1_000_0000000);
76+
}
77+
78+
#[test]
79+
fn test_bulk_mint_multiple_tokens() {
80+
let env = Env::default();
81+
env.mock_all_auths();
82+
83+
let admin = Address::generate(&env);
84+
let user = Address::generate(&env);
85+
86+
let token_a = create_test_token(&env, &admin);
87+
let token_b = create_test_token(&env, &admin);
88+
let client_a = TokenClient::new(&env, &token_a);
89+
let client_b = TokenClient::new(&env, &token_b);
90+
91+
let (faucet, _) = create_faucet(&env);
92+
faucet.initialize(&admin);
93+
94+
let requests = Vec::from_array(
95+
&env,
96+
[
97+
MintRequest {
98+
token: token_a.clone(),
99+
to: user.clone(),
100+
amount: 500_0000000,
101+
},
102+
MintRequest {
103+
token: token_b.clone(),
104+
to: user.clone(),
105+
amount: 100_0000000,
106+
},
107+
],
108+
);
109+
110+
faucet.bulk_mint(&requests);
111+
112+
assert_eq!(client_a.balance(&user), 500_0000000);
113+
assert_eq!(client_b.balance(&user), 100_0000000);
114+
}
115+
116+
#[test]
117+
fn test_bulk_mint_multiple_recipients() {
118+
let env = Env::default();
119+
env.mock_all_auths();
120+
121+
let admin = Address::generate(&env);
122+
let user_a = Address::generate(&env);
123+
let user_b = Address::generate(&env);
124+
125+
let token_addr = create_test_token(&env, &admin);
126+
let token_client = TokenClient::new(&env, &token_addr);
127+
128+
let (faucet, _) = create_faucet(&env);
129+
faucet.initialize(&admin);
130+
131+
let requests = Vec::from_array(
132+
&env,
133+
[
134+
MintRequest {
135+
token: token_addr.clone(),
136+
to: user_a.clone(),
137+
amount: 200_0000000,
138+
},
139+
MintRequest {
140+
token: token_addr.clone(),
141+
to: user_b.clone(),
142+
amount: 300_0000000,
143+
},
144+
],
145+
);
146+
147+
faucet.bulk_mint(&requests);
148+
149+
assert_eq!(token_client.balance(&user_a), 200_0000000);
150+
assert_eq!(token_client.balance(&user_b), 300_0000000);
151+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
use soroban_sdk::{contracttype, Address};
2+
3+
#[contracttype]
4+
#[derive(Clone, Debug)]
5+
pub struct MintRequest {
6+
pub token: Address,
7+
pub to: Address,
8+
pub amount: i128,
9+
}

apps/contracts/stellar-contracts/rwa-token/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ doctest = false
1414

1515
[dependencies]
1616
soroban-sdk = { workspace = true }
17-
rwa-oracle = { path = "../rwa-oracle" }
1817

1918
[dev-dependencies]
2019
soroban-sdk = { workspace = true, features = ["testutils"] }
20+
rwa-oracle = { path = "../rwa-oracle" }
2121

2222
[package.metadata.stellar]
2323
contract = true

0 commit comments

Comments
 (0)