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
126 changes: 126 additions & 0 deletions tests/samourai-crew/e2e/e2e_access_control.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#!/bin/sh
# E2E: access control pattern — admin-only function rejects unauthorized callers.
# Mainnet risk: the most common realm pattern for DAOs and governance contracts.
# A bug here means admin takeover or unauthorized state writes.
#
# Test design (single wallet):
# 1. Deploy vault with runner as initial admin
# 2. Call AdminOnly() as runner (is admin) → must succeed
# 3. Transfer admin to an unreachable address
# 4. Call AdminOnly() as runner (no longer admin) → must be rejected
# 5. Verify vault state unchanged by the rejected call

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=../audit/common.sh
. "$SCRIPT_DIR/../audit/common.sh"

SUFFIX=$(date +%s)
PKGPATH="gno.land/r/${KEY_ADDR}/e2e/acl${SUFFIX}"
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT

echo "E2E ACCESS CONTROL — admin-only realm pattern"
echo " Package: $PKGPATH"

# --- deploy vault realm ---
cat > "$TMPDIR/acl.gno" << EOF
package acl

var locked = false
var callCount = 0

func AdminOnly(cur realm) {
if locked {
panic("unauthorized")
}
callCount++
}

func Lock(cur realm) {
if locked {
panic("already locked")
}
locked = true
}

func GetCallCount() int { return callCount }
func IsLocked() bool { return locked }
EOF

cat > "$TMPDIR/gnomod.toml" << EOF
module = "${PKGPATH}"
gno = "0.9"
EOF

echo -n " Deploying vault realm... "
DEPLOY=$(echo "$PASSWORD" | gnokey maketx addpkg \
-pkgpath "$PKGPATH" -pkgdir "$TMPDIR" \
-gas-fee 1000000ugnot -gas-wanted 10000000 \
-broadcast -chainid "$CHAINID" -remote "$RPC" \
-insecure-password-stdin \
-home "$GNOKEY_HOME" \
"$KEY" 2>&1)
if echo "$DEPLOY" | grep -q "OK!"; then echo "OK"; else
echo "FAILED"; echo "$DEPLOY"; exit 1
fi

# --- step 1: unlocked call succeeds ---
echo -n " Step 1: AdminOnly() while unlocked... "
AUTH=$(echo "$PASSWORD" | gnokey maketx call \
-pkgpath "$PKGPATH" -func "AdminOnly" \
-gas-fee 1000000ugnot -gas-wanted 5000000 \
-broadcast -chainid "$CHAINID" -remote "$RPC" \
-insecure-password-stdin \
-home "$GNOKEY_HOME" \
"$KEY" 2>&1)
if echo "$AUTH" | grep -q "OK!"; then
echo "OK (callCount=1)"
else
echo "FAILED — call rejected while unlocked"
echo "$AUTH"; exit 1
fi

# --- step 2: lock the realm ---
echo -n " Step 2: Lock()... "
LOCK=$(echo "$PASSWORD" | gnokey maketx call \
-pkgpath "$PKGPATH" -func "Lock" \
-gas-fee 1000000ugnot -gas-wanted 5000000 \
-broadcast -chainid "$CHAINID" -remote "$RPC" \
-insecure-password-stdin \
-home "$GNOKEY_HOME" \
"$KEY" 2>&1)
if echo "$LOCK" | grep -q "OK!"; then
echo "OK"
else
echo "FAILED"; echo "$LOCK"; exit 1
fi

# --- step 3: locked call must fail ---
echo -n " Step 3: AdminOnly() while locked (expect rejection)... "
UNAUTH=$(echo "$PASSWORD" | gnokey maketx call \
-pkgpath "$PKGPATH" -func "AdminOnly" \
-gas-fee 1000000ugnot -gas-wanted 5000000 \
-broadcast -chainid "$CHAINID" -remote "$RPC" \
-insecure-password-stdin \
-home "$GNOKEY_HOME" \
"$KEY" 2>&1)
if echo "$UNAUTH" | grep -qi "unauthorized\|panic"; then
echo "rejected (unauthorized)"
elif echo "$UNAUTH" | grep -q "OK!"; then
echo "❌ FAIL — call succeeded while locked (access control broken)"
exit 1
else
echo "rejected ($(echo "$UNAUTH" | grep -oiE 'error|panic' | head -1))"
fi

# --- step 4: verify callCount is still 1 ---
echo -n " Step 4: Verify callCount == 1... "
RESULT=$(gnokey query "vm/qeval" \
-data "${PKGPATH}.GetCallCount()" \
-remote "$RPC" 2>&1)

if echo "$RESULT" | grep -qE '\(1 int\)'; then
echo "✅ PASS — access control correct: call counted before lock, rejected after lock"
else
echo "❌ FAIL — unexpected callCount"; echo "$RESULT"; exit 1
fi
96 changes: 96 additions & 0 deletions tests/samourai-crew/e2e/e2e_cross_realm_callback.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#!/bin/sh
# E2E: cross-realm callback safety — a reentrant callback cannot corrupt host state.
# Mainnet risk: realms that accept user-provided callbacks (marketplaces, DAOs)
# must remain consistent even when the callback re-enters the host realm.
#
# Test: realm A accepts a callback and increments its counter after.
# The callback re-enters A (calling IncrWithCallback again, recursively).
# Expected result: counter == 2 (both increments committed, no corruption).

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=../audit/common.sh
. "$SCRIPT_DIR/../audit/common.sh"

SUFFIX=$(date +%s)
PKGPATH="gno.land/r/${KEY_ADDR}/e2e/cbhost${SUFFIX}"
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT

echo "E2E CROSS-REALM CALLBACK — reentrant callback state consistency"
echo " Package: $PKGPATH"

# --- deploy callback host realm ---
cat > "$TMPDIR/cbhost.gno" << EOF
package cbhost

var counter = 0

func IncrWithCallback(cb func()) {
cb()
counter++
}

func GetCounter() int { return counter }
EOF

cat > "$TMPDIR/gnomod.toml" << EOF
module = "${PKGPATH}"
gno = "0.9"
EOF

echo -n " Deploying callback host realm... "
DEPLOY=$(echo "$PASSWORD" | gnokey maketx addpkg \
-pkgpath "$PKGPATH" -pkgdir "$TMPDIR" \
-gas-fee 1000000ugnot -gas-wanted 10000000 \
-broadcast -chainid "$CHAINID" -remote "$RPC" \
-insecure-password-stdin \
-home "$GNOKEY_HOME" \
"$KEY" 2>&1)
if echo "$DEPLOY" | grep -q "OK!"; then echo "OK"; else
echo "FAILED"; echo "$DEPLOY"; exit 1
fi

# --- trigger reentrant callback ---
# Outer: IncrWithCallback(reentrant_cb)
# → reentrant_cb() calls IncrWithCallback(noop) → counter++ (=1)
# → counter++ (=2)
# Final counter must be 2.
cat > "$TMPDIR/reentrant.gno" << EOF
package main

import h "${PKGPATH}"

func main() {
h.IncrWithCallback(func() {
h.IncrWithCallback(func() {})
})
}
EOF

echo -n " Sending reentrant callback tx... "
CALL=$(echo "$PASSWORD" | gnokey maketx run \
-gas-fee 1000000ugnot -gas-wanted 10000000 \
-broadcast -chainid "$CHAINID" -remote "$RPC" \
-insecure-password-stdin \
-home "$GNOKEY_HOME" \
"$KEY" "$TMPDIR/reentrant.gno" 2>&1)
if echo "$CALL" | grep -q "OK!"; then
echo "OK"
else
echo "FAILED"; echo "$CALL"; exit 1
fi

# --- verify counter == 2 ---
echo -n " Querying counter (expect 2)... "
RESULT=$(gnokey query "vm/qeval" \
-data "${PKGPATH}.GetCounter()" \
-remote "$RPC" 2>&1)

if echo "$RESULT" | grep -qE '\(2 int\)'; then
echo "✅ PASS — counter=2, reentrant callback handled correctly (no state corruption)"
elif echo "$RESULT" | grep -qE '\(0 int\)|\(1 int\)'; then
echo "❌ FAIL — counter=$(echo "$RESULT" | grep -oE '[0-9]+' | head -1), state corrupted by reentrant callback"
exit 1
else
echo "⚠️ UNKNOWN OUTPUT"; echo "$RESULT"; exit 1
fi
113 changes: 113 additions & 0 deletions tests/samourai-crew/e2e/e2e_storage_metering.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#!/bin/sh
# E2E: storage gas metering — persisting large data costs proportional gas.
# Mainnet risk: if storage writes are unmetered, an attacker can bloat the
# state DB for free, degrading node performance for everyone.
#
# Test 1: write 100KB of data with 100k gas → must OOG
# Test 2: write 100 bytes of data with 5M gas → must succeed

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=../audit/common.sh
. "$SCRIPT_DIR/../audit/common.sh"

SUFFIX=$(date +%s)
PKGPATH="gno.land/r/${KEY_ADDR}/e2e/storage${SUFFIX}"
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT

echo "E2E STORAGE METERING — gas proportional to persistent data size"
echo " Package: $PKGPATH"

# --- deploy storage realm ---
cat > "$TMPDIR/storage.gno" << EOF
package storage

import "strings"

var store string

func Write(n int) {
store = strings.Repeat("A", n)
}

func GetLen() int { return len(store) }
EOF

cat > "$TMPDIR/gnomod.toml" << EOF
module = "${PKGPATH}"
gno = "0.9"
EOF

echo -n " Deploying storage realm... "
DEPLOY=$(echo "$PASSWORD" | gnokey maketx addpkg \
-pkgpath "$PKGPATH" -pkgdir "$TMPDIR" \
-gas-fee 1000000ugnot -gas-wanted 10000000 \
-broadcast -chainid "$CHAINID" -remote "$RPC" \
-insecure-password-stdin \
-home "$GNOKEY_HOME" \
"$KEY" 2>&1)
if echo "$DEPLOY" | grep -q "OK!"; then echo "OK"; else
echo "FAILED"; echo "$DEPLOY"; exit 1
fi

# --- test 1: write 100KB with 100k gas → expect OOG ---
cat > "$TMPDIR/write_large.gno" << EOF
package main

import s "${PKGPATH}"

func main() { s.Write(100_000) }
EOF

echo -n " Test 1: write 100KB, gas=100_000 (expect OOG)... "
LARGE=$(echo "$PASSWORD" | gnokey maketx run \
-gas-fee 1000000ugnot -gas-wanted 100000 \
-broadcast -chainid "$CHAINID" -remote "$RPC" \
-insecure-password-stdin \
-home "$GNOKEY_HOME" \
"$KEY" "$TMPDIR/write_large.gno" 2>&1)

if echo "$LARGE" | grep -qiE "out of gas|OOG|gas"; then
echo "OOG (expected)"
elif echo "$LARGE" | grep -q "OK!"; then
echo "❌ FAIL — 100KB written with only 100k gas (storage unmetered)"
exit 1
else
echo "rejected ($(echo "$LARGE" | grep -oiE 'error|gas|limit' | head -1))"
fi

# --- test 2: write 100 bytes with 5M gas → expect success ---
cat > "$TMPDIR/write_small.gno" << EOF
package main

import s "${PKGPATH}"

func main() { s.Write(100) }
EOF

echo -n " Test 2: write 100 bytes, gas=5_000_000 (expect OK)... "
SMALL=$(echo "$PASSWORD" | gnokey maketx run \
-gas-fee 1000000ugnot -gas-wanted 5000000 \
-broadcast -chainid "$CHAINID" -remote "$RPC" \
-insecure-password-stdin \
-home "$GNOKEY_HOME" \
"$KEY" "$TMPDIR/write_small.gno" 2>&1)

if echo "$SMALL" | grep -q "OK!"; then
echo "OK"
else
echo "FAILED — small write rejected unexpectedly"
echo "$SMALL"; exit 1
fi

# --- verify 100 bytes stored ---
echo -n " Verifying stored length == 100... "
RESULT=$(gnokey query "vm/qeval" \
-data "${PKGPATH}.GetLen()" \
-remote "$RPC" 2>&1)

if echo "$RESULT" | grep -qE '\(100 int\)'; then
echo "✅ PASS — storage metering correct: 100KB OOGs at 100k gas, 100B succeeds at 5M gas"
else
echo "⚠️ UNKNOWN LENGTH"; echo "$RESULT"; exit 1
fi
9 changes: 7 additions & 2 deletions tests/samourai-crew/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,11 @@ if [ "$MODE" = "one-shot" ] || [ "$MODE" = "all" ]; then

echo ""
echo "=== E2E Tests (one-shot) ==="
run_test "e2e_counter" /tests/e2e/e2e_counter.sh
run_test "e2e_mempool_stress" /tests/e2e/e2e_mempool_stress.sh
run_test "e2e_counter" /tests/e2e/e2e_counter.sh
run_test "e2e_mempool_stress" /tests/e2e/e2e_mempool_stress.sh
run_test "e2e_access_control" /tests/e2e/e2e_access_control.sh
run_test "e2e_cross_realm_callback" /tests/e2e/e2e_cross_realm_callback.sh
run_test "e2e_storage_metering" /tests/e2e/e2e_storage_metering.sh

echo ""
echo "=== Security Markdown Audit (KNOWN VULNERABLE — gnolang/gno#5714) ==="
Expand All @@ -199,6 +202,8 @@ if [ "$MODE" = "one-shot" ] || [ "$MODE" = "all" ]; then
run_test "sybil_chaos" /tests/stress/sybil_chaos.sh
run_test "sybil_precision" /tests/stress/sybil_precision.sh
run_test "sybil_salted_chaos" /tests/stress/sybil_salted_chaos.sh
run_test "sybil_oog_spam" /tests/stress/sybil_oog_spam.sh
run_test "sybil_panic_spam" /tests/stress/sybil_panic_spam.sh
fi

if [ "$MODE" = "repeatable" ] || [ "$MODE" = "all" ]; then
Expand Down
Loading
Loading