Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 0 additions & 30 deletions soroban/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

171 changes: 93 additions & 78 deletions soroban/scripts/deploy.sh
Original file line number Diff line number Diff line change
@@ -1,103 +1,118 @@
#!/usr/bin/env bash
set -euo pipefail

# Deploy TestStablecoin to Stellar testnet
#
# Prerequisites:
# - stellar CLI installed (https://developers.stellar.org/docs/tools/developer-tools/cli/install-cli)
# - An identity configured: stellar keys generate <name> --network testnet
# or import existing: stellar keys add <name> --secret-key
# TEST ONLY: deploy a private classic asset, its SAC, and the minimal test
# administration wrapper. This script is not suitable for production assets.
#
# Usage:
# ./deploy.sh <identity> [admin_address] [manager_address] [blocker_address]
# STELLAR_NETWORK=testnet ./deploy.sh <issuer-key> [deployer-key] \
# [minter-address] [onboarder-address] [blocker-address] [unblocker-address]
#
# Defaults:
# - deployer-key defaults to issuer-key
# - every role defaults to the issuer's public address
# - ASSET_CODE defaults to TSTUSD
#
# Examples:
# ./deploy.sh alice # alice is admin, manager, and blocker
# ./deploy.sh alice GA...ADMIN GA...MANAGER GA...BLOCKER # separate roles
# Mainnet requires:
# STELLAR_NETWORK=mainnet \
# ALLOW_MAINNET_TEST_DEPLOY=I_UNDERSTAND_TEST_ONLY \
# ./deploy.sh ...

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SOROBAN_DIR="$(dirname "$SCRIPT_DIR")"

NETWORK="testnet"
TOKEN_NAME="Test USD"
TOKEN_SYMBOL="TUSD"
INITIAL_SUPPLY="1000000000" # 1000 tokens with 6 decimals

# --- Parse args ---

if [ $# -lt 1 ]; then
echo "Usage: $0 <identity> [admin_address] [manager_address] [blocker_address]"
echo ""
echo " identity Stellar CLI identity name (from 'stellar keys')"
echo " admin_address Admin address (defaults to identity's address)"
echo " manager_address Manager/compliance address (defaults to admin)"
echo " blocker_address Blocker/freeze address (defaults to admin)"
if [[ $# -lt 1 || $# -gt 6 ]]; then
echo "Usage: $0 <issuer-key> [deployer-key] [minter-address] [onboarder-address] [blocker-address] [unblocker-address]" >&2
exit 1
fi

IDENTITY="$1"
ADMIN_ADDRESS="${2:-}"
MANAGER_ADDRESS="${3:-}"
BLOCKER_ADDRESS="${4:-}"
ISSUER_KEY="$1"
DEPLOYER_KEY="${2:-$ISSUER_KEY}"
NETWORK="${STELLAR_NETWORK:-testnet}"
ASSET_CODE="${ASSET_CODE:-TSTUSD}"
ISSUER_ADDRESS="$(stellar keys address "$ISSUER_KEY")"

# Resolve addresses from identity if not provided
if [ -z "$ADMIN_ADDRESS" ]; then
ADMIN_ADDRESS=$(stellar keys address "$IDENTITY")
echo "Using identity address as admin: $ADMIN_ADDRESS"
fi
MINTER_ADDRESS="${3:-$ISSUER_ADDRESS}"
ONBOARDER_ADDRESS="${4:-$ISSUER_ADDRESS}"
BLOCKER_ADDRESS="${5:-$ISSUER_ADDRESS}"
UNBLOCKER_ADDRESS="${6:-$ISSUER_ADDRESS}"

if [ -z "$MANAGER_ADDRESS" ]; then
MANAGER_ADDRESS="$ADMIN_ADDRESS"
echo "Using admin address as manager: $MANAGER_ADDRESS"
if [[ "$NETWORK" == "mainnet" && "${ALLOW_MAINNET_TEST_DEPLOY:-}" != "I_UNDERSTAND_TEST_ONLY" ]]; then
echo "Refusing mainnet deployment without ALLOW_MAINNET_TEST_DEPLOY=I_UNDERSTAND_TEST_ONLY" >&2
exit 1
fi

if [ -z "$BLOCKER_ADDRESS" ]; then
BLOCKER_ADDRESS="$ADMIN_ADDRESS"
echo "Using admin address as blocker: $BLOCKER_ADDRESS"
if [[ ! "$ASSET_CODE" =~ ^[A-Z0-9]{1,12}$ ]]; then
echo "ASSET_CODE must contain 1-12 uppercase letters or digits" >&2
exit 1
fi

# --- Build ---
echo "WARNING: deploying an unaudited TEST-ONLY contract."
echo "Network: $NETWORK"
echo "Asset: $ASSET_CODE:$ISSUER_ADDRESS"
echo

echo ""
echo "Building test-stablecoin..."
cd "$SOROBAN_DIR"
cargo build --release --target wasm32-unknown-unknown

echo "Optimizing WASM for Soroban VM..."
stellar contract optimize --wasm "$SOROBAN_DIR/target/wasm32-unknown-unknown/release/test_stablecoin.wasm"
WASM_PATH="$SOROBAN_DIR/target/wasm32-unknown-unknown/release/test_stablecoin.optimized.wasm"

if [ ! -f "$WASM_PATH" ]; then
echo "ERROR: Optimized WASM not found at $WASM_PATH"
echo "[1/5] Setting issuer authorization flags..."
stellar tx new set-options \
--source-account "$ISSUER_KEY" \
--network "$NETWORK" \
--set-required \
--set-revocable \
--set-clawback-enabled

echo "[2/5] Deploying the Stellar Asset Contract..."
SAC_CONTRACT_ID="$(
stellar contract asset deploy \
--source-account "$DEPLOYER_KEY" \
--network "$NETWORK" \
--asset "$ASSET_CODE:$ISSUER_ADDRESS" |
tr -d '\r\n'
)"

echo "[3/5] Building and optimizing the test wrapper..."
(
cd "$SOROBAN_DIR"
cargo build --release --target wasm32-unknown-unknown --package test-stablecoin
)
RAW_WASM="$SOROBAN_DIR/target/wasm32-unknown-unknown/release/test_stablecoin.wasm"
OPTIMIZED_WASM="$SOROBAN_DIR/target/wasm32-unknown-unknown/release/test_stablecoin.optimized.wasm"
stellar contract optimize --wasm "$RAW_WASM"

if [[ ! -f "$OPTIMIZED_WASM" ]]; then
echo "Optimized WASM not found at $OPTIMIZED_WASM" >&2
exit 1
fi

echo "WASM size: $(wc -c < "$WASM_PATH" | tr -d ' ') bytes (optimized)"

# --- Deploy + Initialize ---

echo ""
echo "Deploying to $NETWORK (name=$TOKEN_NAME, symbol=$TOKEN_SYMBOL, supply=$INITIAL_SUPPLY)..."
CONTRACT_ID=$(stellar contract deploy \
--wasm "$WASM_PATH" \
--source "$IDENTITY" \
echo "[4/5] Deploying the test administration wrapper..."
WRAPPER_CONTRACT_ID="$(
stellar contract deploy \
--source-account "$DEPLOYER_KEY" \
--network "$NETWORK" \
--wasm "$OPTIMIZED_WASM" \
-- \
--sac_token "$SAC_CONTRACT_ID" \
--minter "$MINTER_ADDRESS" \
--onboarder "$ONBOARDER_ADDRESS" \
--block_operator "$BLOCKER_ADDRESS" \
--unblock_operator "$UNBLOCKER_ADDRESS" |
tr -d '\r\n'
)"

echo "[5/5] Transferring SAC administration to the wrapper..."
stellar contract invoke \
--source-account "$ISSUER_KEY" \
--network "$NETWORK" \
--id "$SAC_CONTRACT_ID" \
-- \
--name "\"${TOKEN_NAME}\"" \
--symbol "\"${TOKEN_SYMBOL}\"" \
--admin "$ADMIN_ADDRESS" \
--manager "$MANAGER_ADDRESS" \
--blocker "$BLOCKER_ADDRESS" \
--initial_supply "$INITIAL_SUPPLY")

echo "Contract deployed: $CONTRACT_ID"

echo ""
echo "=== Deployment complete ==="
echo " Network: $NETWORK"
echo " Contract: $CONTRACT_ID"
echo " Admin: $ADMIN_ADDRESS"
echo " Manager: $MANAGER_ADDRESS"
echo " Blocker: $BLOCKER_ADDRESS"
echo " Token: $TOKEN_NAME ($TOKEN_SYMBOL)"
echo " Supply: $INITIAL_SUPPLY (6 decimals = 1,000 tokens)"
set_admin --new_admin "$WRAPPER_CONTRACT_ID"
Comment on lines +103 to +107

echo
echo "Test deployment complete"
echo " Network: $NETWORK"
echo " Asset: $ASSET_CODE:$ISSUER_ADDRESS"
echo " SAC: $SAC_CONTRACT_ID"
echo " Wrapper: $WRAPPER_CONTRACT_ID"
echo " Minter: $MINTER_ADDRESS"
echo " Onboarder: $ONBOARDER_ADDRESS"
echo " Block operator: $BLOCKER_ADDRESS"
echo " Unblock operator: $UNBLOCKER_ADDRESS"
4 changes: 1 addition & 3 deletions soroban/test-stablecoin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@
name = "test-stablecoin"
version = "0.1.0"
edition = "2021"
description = "TEST ONLY: minimal SAC onboarding and block-list integration harness"

[lib]
crate-type = ["cdylib"]
doctest = false

[dependencies]
# Pinned to 23.5.3 for compatibility with OpenZeppelin Stellar Contracts 0.6.0
soroban-sdk = "23.5.3"
stellar-tokens = "0.6.0"
stellar-access = "0.6.0"

[dev-dependencies]
soroban-sdk = { version = "23.5.3", features = ["testutils"] }
80 changes: 80 additions & 0 deletions soroban/test-stablecoin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Test SAC Admin Contract

> TEST ONLY. This contract is unaudited, intentionally incomplete, and not
> production-ready. Do not use it to administer assets of value.

This package is a small Soroban integration harness for testing a private
Stellar asset. It administers a Stellar Asset Contract (SAC) whose issuer has
`AUTH_REQUIRED`, `AUTH_REVOCABLE`, and `AUTH_CLAWBACK_ENABLED` set.

It is an independent test implementation built against Stellar's public SAC
interfaces. It is not affiliated with, endorsed by, or a production substitute
for M0, MoneyGram, MGUSD, or their contracts.

## State model

The contract intentionally keeps three related states:

- `is_onboarded(address)` is permanent onboarding history.
- `is_on_block_list(address)` is the current compliance hold.
- `blocked(address)` is the inverse of the SAC's current authorization flag.

These states can differ. In particular:

- A fresh trustline is not onboarded, not block-listed, and SAC-blocked.
- Blocking before onboarding and then unblocking clears the hold but does not
authorize the address.
- Onboarding, blocking, and then unblocking restores authorization because the
onboarding record persists.
- Deleting and recreating a trustline resets SAC authorization. Calling
`onboard_user` again reauthorizes it without emitting a second onboarding
event.

## Public test API

- `onboard_user(user, operator)`
- `block_user(user, operator)`
- `unblock_user(user, operator)`
- `mint(caller, to, amount)`
- `is_onboarded(account)`
- `is_onboarder(account)`
- `is_block_operator(account)`
- `is_unblock_operator(account)`
- `is_on_block_list(account)`
- `blocked(account)`
- `balance(account)`
- `sac_token()`

The onboarder, block operator, unblock operator, and minter are fixed at
deployment. One wallet may hold every role.

Transfers are made directly through the SAC. This wrapper intentionally does
not proxy the SEP-41 transfer interface.

## Deliberate omissions

The contract has no batch operations, role rotation, burn endpoint, forced
transfer, pause, upgrade, yield, supply accounting, or issuer-renunciation
logic. Those omissions keep the artifact focused on onboarding and compliance
state permutations.

## Deployment

Use `../scripts/deploy.sh`. The script:

1. Sets the required issuer flags.
2. Deploys the SAC for the configured classic asset.
3. Builds and deploys this wrapper.
4. Transfers SAC administration to the wrapper.

The script defaults all identities and roles to one wallet. Mainnet execution
requires an explicit `ALLOW_MAINNET_TEST_DEPLOY=I_UNDERSTAND_TEST_ONLY`
acknowledgement.

## License

This package is part of the Predicate contracts repository and is distributed
under the repository's MIT license. Functional behavior was independently
implemented from public Stellar interfaces. Similarity in behavior to another
system does not establish legal clearance; consult counsel if licensing risk is
material to deployment or distribution.
Loading
Loading