Add blockchain smart contract lab

This commit is contained in:
juvdiaz 2026-06-29 19:15:09 -06:00
parent bedb845270
commit 945b2a09a7
10 changed files with 203 additions and 0 deletions

View File

@ -284,6 +284,7 @@ Run a read-only health snapshot from the Debian server with:
./jeannie resource-budget ./jeannie resource-budget
./jeannie artifact-cache status ./jeannie artifact-cache status
./jeannie blockchain-devnet status ./jeannie blockchain-devnet status
./jeannie blockchain-test
./jeannie golden-ledger check ./jeannie golden-ledger check
./jeannie route-inventory ./jeannie route-inventory
./jeannie workers list ./jeannie workers list
@ -374,6 +375,10 @@ cache. Use `artifact-cache instructions` for client configuration snippets.
`infra/blockchain-devnet`. It is for contract, wallet, RPC, and observability `infra/blockchain-devnet`. It is for contract, wallet, RPC, and observability
learning only; never use real seed phrases or mainnet keys. learning only; never use real seed phrases or mainnet keys.
`blockchain-test` runs the Foundry tests in `labs/blockchain`. The lab starts
with a simple `Counter` contract and ownership tests so there is a known-good
baseline before practicing vulnerable contracts.
`golden-ledger` shows or validates `infra/pimox/golden-image-ledger.yml`, the `golden-ledger` shows or validates `infra/pimox/golden-image-ledger.yml`, the
reviewable record of template VMID, storage, OS release, Kubernetes pins, reviewable record of template VMID, storage, OS release, Kubernetes pins,
runtime versions, and build metadata for Pimox worker golden images. runtime versions, and build metadata for Pimox worker golden images.

View File

@ -284,6 +284,7 @@ Run a read-only health snapshot from the Debian server with:
./{{ main_script }} resource-budget ./{{ main_script }} resource-budget
./{{ main_script }} artifact-cache status ./{{ main_script }} artifact-cache status
./{{ main_script }} blockchain-devnet status ./{{ main_script }} blockchain-devnet status
./{{ main_script }} blockchain-test
./{{ main_script }} golden-ledger check ./{{ main_script }} golden-ledger check
./{{ main_script }} route-inventory ./{{ main_script }} route-inventory
./{{ main_script }} workers list ./{{ main_script }} workers list
@ -374,6 +375,10 @@ cache. Use `artifact-cache instructions` for client configuration snippets.
`infra/blockchain-devnet`. It is for contract, wallet, RPC, and observability `infra/blockchain-devnet`. It is for contract, wallet, RPC, and observability
learning only; never use real seed phrases or mainnet keys. learning only; never use real seed phrases or mainnet keys.
`blockchain-test` runs the Foundry tests in `labs/blockchain`. The lab starts
with a simple `Counter` contract and ownership tests so there is a known-good
baseline before practicing vulnerable contracts.
`golden-ledger` shows or validates `infra/pimox/golden-image-ledger.yml`, the `golden-ledger` shows or validates `infra/pimox/golden-image-ledger.yml`, the
reviewable record of template VMID, storage, OS release, Kubernetes pins, reviewable record of template VMID, storage, OS release, Kubernetes pins,
runtime versions, and build metadata for Pimox worker golden images. runtime versions, and build metadata for Pimox worker golden images.

View File

@ -127,6 +127,10 @@ Docker Hub pull-through image caching.
`infra/blockchain-devnet`. This is for local learning only; never use real seed `infra/blockchain-devnet`. This is for local learning only; never use real seed
phrases or mainnet keys. phrases or mainnet keys.
`blockchain-test [forge args...]`
: Run the Foundry smart contract tests in `labs/blockchain`. Extra arguments are
passed through to `forge test`.
`golden-ledger {show|check}` `golden-ledger {show|check}`
: Show or validate the Pimox golden image version ledger. : Show or validate the Pimox golden image version ledger.

View File

@ -6012,6 +6012,10 @@ blockchain_devnet() {
"${REPO_ROOT}/scripts/blockchain-devnet" "${@:2}" "${REPO_ROOT}/scripts/blockchain-devnet" "${@:2}"
} }
blockchain_test() {
"${REPO_ROOT}/scripts/blockchain-test" "${@:2}"
}
golden_ledger() { golden_ledger() {
"${REPO_ROOT}/scripts/golden-ledger" "${@:2}" "${REPO_ROOT}/scripts/golden-ledger" "${@:2}"
} }
@ -6080,6 +6084,7 @@ Build And Bootstrap
Manage optional Debian artifact caches. Manage optional Debian artifact caches.
blockchain-devnet {status|up|down|logs|rpc} blockchain-devnet {status|up|down|logs|rpc}
Manage the local Ethereum Anvil devnet. Manage the local Ethereum Anvil devnet.
blockchain-test [forge args...] Run Foundry tests for labs/blockchain.
golden-ledger {show|check} Show or validate Pimox golden image versions. golden-ledger {show|check} Show or validate Pimox golden image versions.
Cluster Lifecycle Cluster Lifecycle
@ -6218,6 +6223,9 @@ case "${1:-}" in
blockchain-devnet) blockchain-devnet)
blockchain_devnet "$@" blockchain_devnet "$@"
;; ;;
blockchain-test)
blockchain_test "$@"
;;
golden-ledger) golden-ledger)
golden_ledger "$@" golden_ledger "$@"
;; ;;

44
labs/blockchain/README.md Normal file
View File

@ -0,0 +1,44 @@
# Blockchain Contract Lab
This is the local smart contract workspace for the homelab. It uses Foundry
because it is fast, test-oriented, and works well with a local Anvil devnet.
Never put real private keys, seed phrases, or funded wallet data in this repo.
## Layout
```text
labs/blockchain/
foundry.toml
src/
test/
script/
```
## Test
```bash
./jeannie blockchain-test
```
The wrapper uses a local `forge` binary when available, otherwise it runs the
Foundry Docker image with this directory mounted.
## Deploy To The Local Devnet
Start the local devnet:
```bash
./jeannie blockchain-devnet up
```
Deploy using one of Anvil's generated dev private keys:
```bash
cd labs/blockchain
PRIVATE_KEY=0x... forge script script/DeployCounter.s.sol \
--rpc-url http://127.0.0.1:8545 \
--broadcast
```
Use only Anvil dev keys. Do not paste a real wallet key into a shell.

View File

@ -0,0 +1,12 @@
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
test = "test"
script = "script"
solc_version = "0.8.26"
optimizer = true
optimizer_runs = 200
fs_permissions = [
{ access = "read", path = "./" }
]

View File

@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {Script} from "forge-std/Script.sol";
import {Counter} from "../src/Counter.sol";
contract DeployCounter is Script {
function run() external returns (Counter counter) {
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerKey);
counter = new Counter(0);
vm.stopBroadcast();
}
}

View File

@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Counter {
uint256 public number;
address public owner;
error NotOwner();
event NumberChanged(uint256 previousNumber, uint256 newNumber);
constructor(uint256 initialNumber) {
owner = msg.sender;
number = initialNumber;
}
function setNumber(uint256 newNumber) external {
if (msg.sender != owner) {
revert NotOwner();
}
uint256 previousNumber = number;
number = newNumber;
emit NumberChanged(previousNumber, newNumber);
}
function increment() external {
uint256 previousNumber = number;
number++;
emit NumberChanged(previousNumber, number);
}
}

View File

@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {Test} from "forge-std/Test.sol";
import {Counter} from "../src/Counter.sol";
contract CounterTest is Test {
Counter private counter;
address private owner = address(0xA11CE);
address private stranger = address(0xB0B);
function setUp() public {
vm.prank(owner);
counter = new Counter(41);
}
function testInitialNumberAndOwner() public view {
assertEq(counter.number(), 41);
assertEq(counter.owner(), owner);
}
function testOwnerCanSetNumber() public {
vm.prank(owner);
counter.setNumber(100);
assertEq(counter.number(), 100);
}
function testNonOwnerCannotSetNumber() public {
vm.prank(stranger);
vm.expectRevert(Counter.NotOwner.selector);
counter.setNumber(100);
}
function testAnyoneCanIncrement() public {
vm.prank(stranger);
counter.increment();
assertEq(counter.number(), 42);
}
}

37
scripts/blockchain-test Executable file
View File

@ -0,0 +1,37 @@
#!/usr/bin/env bash
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
LAB_DIR="${REPO_ROOT}/labs/blockchain"
FOUNDRY_IMAGE="${FOUNDRY_IMAGE:-ghcr.io/foundry-rs/foundry:latest}"
usage() {
cat <<'EOF'
Usage: ./jeannie blockchain-test [forge args...]
Run Foundry tests for labs/blockchain. Extra arguments are passed to forge test.
EOF
}
case "${1:-}" in
-h|--help|help)
usage
exit 0
;;
esac
if command -v forge >/dev/null 2>&1; then
cd "$LAB_DIR"
exec forge test "$@"
fi
if ! command -v docker >/dev/null 2>&1; then
echo "forge or docker is required for blockchain-test." >&2
exit 1
fi
exec docker run --rm \
-v "${LAB_DIR}:/workspace" \
-w /workspace \
"$FOUNDRY_IMAGE" \
forge test "$@"