diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ca4d6c4..cc468c8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -6,83 +6,68 @@ on:
paths:
- 'contracts/**'
- 'test/**'
- - 'scripts/**'
- - 'hardhat.config.ts'
- - 'package.json'
- - 'package-lock.json'
+ - 'script/**'
+ - 'foundry.toml'
- '.github/workflows/ci.yml'
pull_request:
branches: ['*']
paths:
- 'contracts/**'
- 'test/**'
- - 'scripts/**'
- - 'hardhat.config.ts'
- - 'package.json'
- - 'package-lock.json'
+ - 'script/**'
+ - 'foundry.toml'
- '.github/workflows/ci.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
-env:
- NODE_VERSION: '24'
-
jobs:
lint-and-format:
name: Lint & Format
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
+ - uses: actions/checkout@v4
- - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
- with:
- node-version: ${{ env.NODE_VERSION }}
- cache: 'npm'
+ - name: Install Foundry
+ uses: foundry-rs/foundry-toolchain@v1
- - run: npm ci --legacy-peer-deps
+ - name: Install dependencies
+ run: forge install --no-git foundry-rs/forge-std OpenZeppelin/openzeppelin-contracts@v5.4.0 FhenixProtocol/cofhe-contracts FhenixProtocol/cofhe-mock-contracts OpenZeppelin/openzeppelin-foundry-upgrades
- name: Check formatting
- run: npm run format:check
-
- - name: Lint Solidity
- run: npm run lint
+ run: forge fmt --check
compile:
name: Compile Contracts
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
+ - uses: actions/checkout@v4
- - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
- with:
- node-version: ${{ env.NODE_VERSION }}
- cache: 'npm'
+ - name: Install Foundry
+ uses: foundry-rs/foundry-toolchain@v1
- - run: npm ci --legacy-peer-deps
+ - name: Install dependencies
+ run: forge install --no-git foundry-rs/forge-std OpenZeppelin/openzeppelin-contracts@v5.4.0 FhenixProtocol/cofhe-contracts FhenixProtocol/cofhe-mock-contracts OpenZeppelin/openzeppelin-foundry-upgrades
- name: Compile contracts
- run: npm run compile
+ run: forge build --sizes
test:
name: Test Contracts
runs-on: ubuntu-latest
needs: compile
timeout-minutes: 15
- env:
- MOCHA_TIMEOUT: '120000'
steps:
- - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
+ - uses: actions/checkout@v4
- - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
- with:
- node-version: ${{ env.NODE_VERSION }}
- cache: 'npm'
+ - name: Install Foundry
+ uses: foundry-rs/foundry-toolchain@v1
- - run: npm ci --legacy-peer-deps
+ - name: Install dependencies
+ run: forge install --no-git foundry-rs/forge-std OpenZeppelin/openzeppelin-contracts@v5.4.0 FhenixProtocol/cofhe-contracts FhenixProtocol/cofhe-mock-contracts OpenZeppelin/openzeppelin-foundry-upgrades
- name: Run tests
- run: npm test
+ run: forge test -vvv
diff --git a/.gitignore b/.gitignore
index bac400e..b30a2b5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
node_modules/
cache/
+cache_forge/
artifacts/
typechain-types/
coverage/
@@ -7,3 +8,8 @@ coverage.json
.env
deployments/*.json
!deployments/.gitkeep
+
+# Foundry
+out/
+broadcast/
+lib/
diff --git a/.husky/pre-commit b/.husky/pre-commit
deleted file mode 100644
index 2312dc5..0000000
--- a/.husky/pre-commit
+++ /dev/null
@@ -1 +0,0 @@
-npx lint-staged
diff --git a/.prettierrc b/.prettierrc
deleted file mode 100644
index 40a55c4..0000000
--- a/.prettierrc
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "semi": true,
- "singleQuote": true,
- "overrides": [
- {
- "files": "*.sol",
- "options": {
- "singleQuote": false
- }
- }
- ],
- "printWidth": 120,
- "tabWidth": 2,
- "trailingComma": "all",
- "plugins": ["prettier-plugin-solidity"]
-}
diff --git a/.solhint.json b/.solhint.json
deleted file mode 100644
index 505b4b0..0000000
--- a/.solhint.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "extends": "solhint:recommended",
- "rules": {
- "compiler-version": ["error", "^0.8.24"],
- "func-visibility": ["warn", { "ignoreConstructors": true }],
- "no-empty-blocks": "off",
- "reason-string": "off",
- "no-unused-vars": "warn"
- }
-}
diff --git a/README.md b/README.md
index 85a4f9b..8817d6a 100644
--- a/README.md
+++ b/README.md
@@ -1,90 +1,66 @@
-# ReineiraOS Code
+## Foundry
-[](https://reineira.xyz)
-[](LICENSE)
+**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**
-AI-assisted plugin development for ReineiraOS. Build condition resolvers and insurance policies with Claude Code.
+Foundry consists of:
-> **Platform 0.1** — Generates contracts compatible with ReineiraOS v0.1 interfaces. Check `reineira.json` for version details.
+- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
+- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
+- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
+- **Chisel**: Fast, utilitarian, and verbose solidity REPL.
-## Setup
+## Documentation
-```bash
-git clone https://github.com/ReineiraOS/reineira-code.git
-cd reineira-code
-npm install --legacy-peer-deps
-cp .env.example .env
-# Add your private key and RPC URL to .env
-```
+https://book.getfoundry.sh/
## Usage
-Open in an editor with Claude Code. Use slash commands:
-
-| Command | What it does |
-| ---------------- | ------------------------------------------------------ |
-| `/new-resolver` | Build a condition resolver from a description |
-| `/new-policy` | Build an insurance policy with FHE from a description |
-| `/deploy` | Deploy any contract to Arbitrum Sepolia |
-| `/test` | Run tests, diagnose and fix failures |
-| `/audit` | Security audit against the protocol checklist |
-| `/integrate` | Generate SDK code to attach your contract to an escrow |
-| `/scaffold-test` | Generate tests for an existing contract |
-| `/verify` | Verify a deployed contract on Arbiscan |
-
-### Example
+### Build
+```shell
+$ forge build
```
-/new-resolver A resolver that verifies PayPal payment via zkTLS proof from Reclaim Protocol
-```
-
-Claude Code generates the Solidity contract, tests, and deployment script — all pre-configured for the ReineiraOS protocol.
-## The ecosystem
+### Test
-| Repo | What you do there | Platform |
-| ------------------------------------------------------------------ | ---------------------------------------------------------- | -------- |
-| [reineira-atlas](https://github.com/ReineiraOS/reineira-atlas) | Run the startup — strategy, ops, growth, compliance, pitch | 0.1 |
-| **reineira-code** (this repo) | Build smart contracts — resolvers, policies, tests, deploy | 0.1 |
-| [platform-modules](https://github.com/ReineiraOS/platform-modules) | Ship the product — backend, platform app, payment link | 0.1 |
+```shell
+$ forge test
+```
-All repos declare their platform compatibility in `reineira.json`. When the platform version bumps, breaking contract interface changes may require upgrading.
+### Format
-## Manual workflow
+```shell
+$ forge fmt
+```
-```bash
-# Compile
-npm run compile
+### Gas Snapshots
-# Test
-npm test
+```shell
+$ forge snapshot
+```
-# Deploy
-CONTRACT_NAME=MyResolver npm run deploy
+### Anvil
-# Verify on Arbiscan
-npx hardhat verify --network arbitrumSepolia
+```shell
+$ anvil
```
-## Compatibility
+### Deploy
-| Component | Requirement |
-| --------- | ----------------------- |
-| Platform | ReineiraOS 0.1 |
-| Solidity | ^0.8.24 |
-| Hardhat | ~2.26.x |
-| SDK | @reineira-os/sdk ^0.1.0 |
-| cofhejs | ^0.3.1 |
-| Node.js | 18+ |
+```shell
+$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key
+```
-## Documentation
+### Cast
-- [ReineiraOS Docs](https://reineira.xyz/docs)
-- [Quick Start](https://reineira.xyz/docs/getting-started/quick-start)
-- [Condition Plugins](https://reineira.xyz/docs/develop/condition-plugins)
-- [Insurance Policies](https://reineira.xyz/docs/develop/insurance-policies)
-- [Telegram](https://t.me/ReineiraOS)
+```shell
+$ cast
+```
-## License
+### Help
-MIT
+```shell
+$ forge --help
+$ anvil --help
+$ cast --help
+```
diff --git a/contracts/interfaces/IConditionResolver.sol b/contracts/interfaces/IConditionResolver.sol
index c1f49b8..0b9371a 100644
--- a/contracts/interfaces/IConditionResolver.sol
+++ b/contracts/interfaces/IConditionResolver.sol
@@ -5,15 +5,15 @@ pragma solidity ^0.8.24;
/// @notice Interface for escrow release condition plugins.
/// @dev Implement this to control when a ConfidentialEscrow releases funds.
interface IConditionResolver {
- /// @notice Check if the release condition for an escrow is met.
- /// @dev Called on every redeem attempt. MUST be a view function.
- /// @param escrowId The sequential escrow identifier.
- /// @return True if the escrow should release funds.
- function isConditionMet(uint256 escrowId) external view returns (bool);
+ /// @notice Check if the release condition for an escrow is met.
+ /// @dev Called on every redeem attempt. MUST be a view function.
+ /// @param escrowId The sequential escrow identifier.
+ /// @return True if the escrow should release funds.
+ function isConditionMet(uint256 escrowId) external view returns (bool);
- /// @notice Initialize condition configuration for a new escrow.
- /// @dev Called atomically during ConfidentialEscrow.create().
- /// @param escrowId The sequential escrow identifier.
- /// @param data ABI-encoded configuration specific to this resolver.
- function onConditionSet(uint256 escrowId, bytes calldata data) external;
+ /// @notice Initialize condition configuration for a new escrow.
+ /// @dev Called atomically during ConfidentialEscrow.create().
+ /// @param escrowId The sequential escrow identifier.
+ /// @param data ABI-encoded configuration specific to this resolver.
+ function onConditionSet(uint256 escrowId, bytes calldata data) external;
}
diff --git a/contracts/interfaces/IUnderwriterPolicy.sol b/contracts/interfaces/IUnderwriterPolicy.sol
index 759672a..309fcdc 100644
--- a/contracts/interfaces/IUnderwriterPolicy.sol
+++ b/contracts/interfaces/IUnderwriterPolicy.sol
@@ -8,23 +8,23 @@ import {euint64, ebool} from "@fhenixprotocol/cofhe-contracts/FHE.sol";
/// @dev Implement this to define risk evaluation and dispute resolution.
/// Return values are FHE-encrypted — the protocol operates on ciphertexts.
interface IUnderwriterPolicy {
- /// @notice Initialize policy-specific data for a new coverage.
- /// @param coverageId The coverage identifier.
- /// @param data ABI-encoded policy configuration.
- function onPolicySet(uint256 coverageId, bytes calldata data) external;
+ /// @notice Initialize policy-specific data for a new coverage.
+ /// @param coverageId The coverage identifier.
+ /// @param data ABI-encoded policy configuration.
+ function onPolicySet(uint256 coverageId, bytes calldata data) external;
- /// @notice Evaluate risk and return an encrypted risk score.
- /// @dev Score is in basis points (0-10000). 100 bps = 1% premium.
- /// MUST call FHE.allowThis() and FHE.allow(value, msg.sender) on return value.
- /// @param escrowId The escrow being insured.
- /// @param riskProof Arbitrary proof data for risk evaluation.
- /// @return riskScore Encrypted risk score in basis points.
- function evaluateRisk(uint256 escrowId, bytes calldata riskProof) external returns (euint64 riskScore);
+ /// @notice Evaluate risk and return an encrypted risk score.
+ /// @dev Score is in basis points (0-10000). 100 bps = 1% premium.
+ /// MUST call FHE.allowThis() and FHE.allow(value, msg.sender) on return value.
+ /// @param escrowId The escrow being insured.
+ /// @param riskProof Arbitrary proof data for risk evaluation.
+ /// @return riskScore Encrypted risk score in basis points.
+ function evaluateRisk(uint256 escrowId, bytes calldata riskProof) external returns (euint64 riskScore);
- /// @notice Judge a dispute and return an encrypted verdict.
- /// @dev MUST call FHE.allowThis() and FHE.allow(value, msg.sender) on return value.
- /// @param coverageId The coverage being disputed.
- /// @param disputeProof Arbitrary proof data from the claimant.
- /// @return valid Encrypted boolean — true if the claim is legitimate.
- function judge(uint256 coverageId, bytes calldata disputeProof) external returns (ebool valid);
+ /// @notice Judge a dispute and return an encrypted verdict.
+ /// @dev MUST call FHE.allowThis() and FHE.allow(value, msg.sender) on return value.
+ /// @param coverageId The coverage being disputed.
+ /// @param disputeProof Arbitrary proof data from the claimant.
+ /// @return valid Encrypted boolean — true if the claim is legitimate.
+ function judge(uint256 coverageId, bytes calldata disputeProof) external returns (ebool valid);
}
diff --git a/contracts/policies/SimpleUnderwriterPolicy.sol b/contracts/policies/SimpleUnderwriterPolicy.sol
new file mode 100644
index 0000000..53ff092
--- /dev/null
+++ b/contracts/policies/SimpleUnderwriterPolicy.sol
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.24;
+
+import {IUnderwriterPolicy} from "../interfaces/IUnderwriterPolicy.sol";
+import {FHE, euint64, ebool} from "@fhenixprotocol/cofhe-contracts/FHE.sol";
+import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
+
+/// @title SimpleUnderwriterPolicy
+/// @notice Simple FHE-based insurance policy for testing
+/// @dev Returns fixed risk scores and validates disputes
+contract SimpleUnderwriterPolicy is IUnderwriterPolicy, ERC165 {
+ struct PolicyConfig {
+ uint64 baseRiskScore;
+ bool configured;
+ }
+
+ mapping(uint256 => PolicyConfig) public policies;
+
+ event PolicySet(uint256 indexed coverageId, uint64 baseRiskScore);
+
+ error PolicyAlreadySet();
+
+ /// @inheritdoc IUnderwriterPolicy
+ function onPolicySet(uint256 coverageId, bytes calldata data) external {
+ if (policies[coverageId].configured) revert PolicyAlreadySet();
+
+ uint64 baseRiskScore = abi.decode(data, (uint64));
+ require(baseRiskScore <= 10000, "Risk score must be <= 10000 bps");
+
+ policies[coverageId] = PolicyConfig({baseRiskScore: baseRiskScore, configured: true});
+
+ emit PolicySet(coverageId, baseRiskScore);
+ }
+
+ /// @inheritdoc IUnderwriterPolicy
+ function evaluateRisk(uint256 coverageId, bytes calldata) external returns (euint64 riskScore) {
+ require(policies[coverageId].configured, "Policy not configured");
+
+ uint64 score = policies[coverageId].baseRiskScore;
+ euint64 encrypted = FHE.asEuint64(score);
+
+ FHE.allowThis(encrypted);
+ FHE.allow(encrypted, msg.sender);
+
+ return encrypted;
+ }
+
+ /// @inheritdoc IUnderwriterPolicy
+ function judge(uint256 coverageId, bytes calldata disputeProof) external returns (ebool valid) {
+ require(policies[coverageId].configured, "Policy not configured");
+
+ // Simple validation: decode proof and check if valid
+ (bool isValid, uint256 timestamp) = abi.decode(disputeProof, (bool, uint256));
+
+ // Check if dispute is recent (within 30 days)
+ bool result = isValid && (block.timestamp - timestamp <= 30 days);
+
+ ebool encrypted = FHE.asEbool(result);
+ FHE.allowThis(encrypted);
+ FHE.allow(encrypted, msg.sender);
+
+ return encrypted;
+ }
+
+ /// @inheritdoc ERC165
+ function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
+ return interfaceId == type(IUnderwriterPolicy).interfaceId || super.supportsInterface(interfaceId);
+ }
+}
diff --git a/contracts/resolvers/TimeLockResolver.sol b/contracts/resolvers/TimeLockResolver.sol
new file mode 100644
index 0000000..a8afa6b
--- /dev/null
+++ b/contracts/resolvers/TimeLockResolver.sol
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.24;
+
+import {IConditionResolver} from "../interfaces/IConditionResolver.sol";
+import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
+
+/// @title TimeLockResolver
+/// @notice Simple time-based condition resolver for testing
+/// @dev Releases escrow after a specified deadline
+contract TimeLockResolver is IConditionResolver, ERC165 {
+ struct Config {
+ uint256 deadline;
+ }
+
+ mapping(uint256 => Config) public configs;
+
+ event ConditionSet(uint256 indexed escrowId, uint256 deadline);
+
+ error InvalidDeadline();
+ error ConditionAlreadySet();
+
+ /// @inheritdoc IConditionResolver
+ function onConditionSet(uint256 escrowId, bytes calldata data) external {
+ if (configs[escrowId].deadline != 0) revert ConditionAlreadySet();
+
+ uint256 deadline = abi.decode(data, (uint256));
+ if (deadline <= block.timestamp) revert InvalidDeadline();
+
+ configs[escrowId] = Config({deadline: deadline});
+ emit ConditionSet(escrowId, deadline);
+ }
+
+ /// @inheritdoc IConditionResolver
+ function isConditionMet(uint256 escrowId) external view returns (bool) {
+ return block.timestamp >= configs[escrowId].deadline;
+ }
+
+ /// @inheritdoc ERC165
+ function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
+ return interfaceId == type(IConditionResolver).interfaceId || super.supportsInterface(interfaceId);
+ }
+}
diff --git a/foundry.lock b/foundry.lock
new file mode 100644
index 0000000..db4cb0c
--- /dev/null
+++ b/foundry.lock
@@ -0,0 +1,32 @@
+{
+ "lib/cofhe-contracts": {
+ "tag": {
+ "name": "v0.1.3",
+ "rev": "cb76c99620e1b4acd395e00672be21e48702429f"
+ }
+ },
+ "lib/cofhe-mock-contracts": {
+ "tag": {
+ "name": "v0.3.1",
+ "rev": "14e5446de3f130bac92b6cd181a24be4559957a0"
+ }
+ },
+ "lib/forge-std": {
+ "tag": {
+ "name": "v1.15.0",
+ "rev": "0844d7e1fc5e60d77b68e469bff60265f236c398"
+ }
+ },
+ "lib/openzeppelin-contracts": {
+ "tag": {
+ "name": "v5.4.0",
+ "rev": "c64a1edb67b6e3f4a15cca8909c9482ad33a02b0"
+ }
+ },
+ "lib/openzeppelin-foundry-upgrades": {
+ "tag": {
+ "name": "v0.4.0",
+ "rev": "cbce1e00305e943aa1661d43f41e5ac72c662b07"
+ }
+ }
+}
\ No newline at end of file
diff --git a/foundry.toml b/foundry.toml
new file mode 100644
index 0000000..d48f56c
--- /dev/null
+++ b/foundry.toml
@@ -0,0 +1,32 @@
+[profile.default]
+src = "contracts"
+out = "out"
+libs = ["lib"]
+test = "test"
+cache_path = "cache_forge"
+
+# Compiler settings matching Hardhat config
+solc_version = "0.8.25"
+evm_version = "cancun"
+optimizer = true
+optimizer_runs = 200
+
+# Remappings for dependencies
+remappings = [
+ "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
+ "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
+ "@fhenixprotocol/cofhe-contracts/=lib/cofhe-contracts/contracts/",
+ "@fhenixprotocol/cofhe-mock-contracts/=lib/cofhe-mock-contracts/contracts/",
+ "openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/",
+ "forge-std/=lib/forge-std/src/"
+]
+
+# RPC endpoints
+[rpc_endpoints]
+arbitrum_sepolia = "${ARBITRUM_SEPOLIA_RPC_URL}"
+
+# Etherscan API keys for verification
+[etherscan]
+arbitrum_sepolia = { key = "${ETHERSCAN_API_KEY}" }
+
+# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
diff --git a/hardhat.config.ts b/hardhat.config.ts
deleted file mode 100644
index 5c9bce8..0000000
--- a/hardhat.config.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import '@nomicfoundation/hardhat-toolbox-viem';
-import '@nomicfoundation/hardhat-ethers';
-import 'cofhe-hardhat-plugin';
-import 'dotenv/config';
-
-import type { HardhatUserConfig } from 'hardhat/config';
-
-const PRIVATE_KEY = process.env.PRIVATE_KEY || '0x' + '0'.repeat(64);
-
-const config: HardhatUserConfig = {
- solidity: {
- version: '0.8.25',
- settings: {
- optimizer: { enabled: true, runs: 200 },
- evmVersion: 'cancun',
- },
- },
- networks: {
- hardhat: {},
- arbitrumSepolia: {
- url: process.env.ARBITRUM_SEPOLIA_RPC_URL || 'https://sepolia-rollup.arbitrum.io/rpc',
- chainId: 421614,
- accounts: [PRIVATE_KEY],
- },
- },
- etherscan: {
- apiKey: {
- arbitrumSepolia: process.env.ETHERSCAN_API_KEY || '',
- },
- },
- mocha: {
- timeout: 120000,
- },
-};
-
-export default config;
diff --git a/package.json b/package.json
index 09e917d..ebddda7 100644
--- a/package.json
+++ b/package.json
@@ -4,55 +4,29 @@
"reineira": {
"platform": "0.1"
},
- "description": "ReineiraOS plugin development environment with Claude Code agents",
+ "description": "ReineiraOS plugin development environment with Foundry",
"private": true,
"license": "MIT",
"scripts": {
- "compile": "hardhat compile",
- "test": "hardhat test",
- "test:resolvers": "hardhat test test/resolvers/",
- "test:policies": "hardhat test test/policies/",
- "clean": "hardhat clean",
- "deploy": "hardhat run scripts/deploy.ts --network arbitrumSepolia",
- "deploy:local": "hardhat run scripts/deploy.ts --network localhost",
- "lint": "solhint 'contracts/**/*.sol'",
- "format": "prettier --write '**/*.{ts,js,json,sol}'",
- "format:check": "prettier --check '**/*.{ts,js,json,sol}'",
- "prepare": "husky"
+ "build": "forge build",
+ "test": "forge test",
+ "test:verbose": "forge test -vvv",
+ "test:gas": "forge test --gas-report",
+ "clean": "forge clean",
+ "deploy:arbitrum": "forge script script/DeployTimeLockResolver.s.sol --rpc-url arbitrum_sepolia --broadcast --verify",
+ "deploy:local": "forge script script/DeployTimeLockResolver.s.sol --rpc-url http://localhost:8545 --broadcast",
+ "snapshot": "forge snapshot",
+ "format": "forge fmt",
+ "format:check": "forge fmt --check",
+ "anvil": "anvil"
},
"dependencies": {
- "@fhenixprotocol/cofhe-contracts": "0.0.13",
- "@fhenixprotocol/cofhe-mock-contracts": "^0.3.1",
- "@openzeppelin/contracts": "^5.4.0",
- "@reineira-os/sdk": "^0.1.0",
- "cofhe-hardhat-plugin": "^0.3.1",
- "cofhejs": "^0.3.1"
+ "@reineira-os/sdk": "^0.1.0"
},
"devDependencies": {
- "@nomicfoundation/hardhat-chai-matchers": "^2.1.0",
- "@nomicfoundation/hardhat-ethers": "~3.0.8",
- "@nomicfoundation/hardhat-ignition-viem": "^3.0.9",
- "@nomicfoundation/hardhat-network-helpers": "^1.1.2",
- "@nomicfoundation/hardhat-toolbox-viem": "^3.0.0",
- "@nomicfoundation/hardhat-verify": "^2.1.3",
- "@nomicfoundation/hardhat-viem": "^2.1.3",
- "@typechain/ethers-v6": "^0.5.1",
- "@typechain/hardhat": "^9.1.0",
- "@types/mocha": "^10.0.10",
- "chai": "^4.5.0",
"dotenv": "^16.4.0",
- "ethers": "^6.16.0",
- "hardhat": "~2.26.1",
- "hardhat-gas-reporter": "^2.3.0",
- "husky": "^9.1.7",
- "lint-staged": "^16.4.0",
"prettier": "^3.4.0",
"prettier-plugin-solidity": "^1.4.0",
- "solhint": "^5.0.0",
- "solidity-coverage": "^0.8.17",
- "ts-node": "^10.9.2",
- "typescript": "^5.9.3",
- "viem": "^2.47.4",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/commit-analyzer": "^13.0.1",
"@semantic-release/git": "^10.0.1",
@@ -60,14 +34,5 @@
"@semantic-release/release-notes-generator": "^14.1.0",
"conventional-changelog-conventionalcommits": "^9.3.0",
"semantic-release": "^25.0.3"
- },
- "lint-staged": {
- "*.sol": [
- "prettier --write",
- "solhint"
- ],
- "*.{ts,js,json}": [
- "prettier --write"
- ]
}
}
diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol
new file mode 100644
index 0000000..c8e758a
--- /dev/null
+++ b/script/Deploy.s.sol
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.24;
+
+import {Script} from "forge-std/Script.sol";
+import {console2} from "forge-std/console2.sol";
+
+/// @title Deploy
+/// @notice Base deployment script for ReineiraOS plugins
+/// @dev Extend this contract to deploy your resolvers or policies
+abstract contract Deploy is Script {
+ /// @notice Deploy a contract to the configured network
+ /// @dev Override this function in your deployment script
+ function run() public virtual;
+
+ /// @notice Get the deployer private key from environment
+ function getDeployerPrivateKey() internal view returns (uint256) {
+ uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
+ require(deployerPrivateKey != 0, "PRIVATE_KEY not set in .env");
+ return deployerPrivateKey;
+ }
+
+ /// @notice Save deployment to JSON file
+ /// @param contractName Name of the deployed contract
+ /// @param contractAddress Address of the deployed contract
+ function saveDeployment(string memory contractName, address contractAddress) internal {
+ string memory network = getNetworkName();
+ string memory deploymentPath = string.concat("deployments/", network, ".json");
+
+ // Create deployment record
+ string memory json = "deployment";
+ vm.serializeString(json, "network", network);
+ vm.serializeAddress(json, "address", contractAddress);
+ vm.serializeAddress(json, "deployer", vm.addr(getDeployerPrivateKey()));
+ vm.serializeUint(json, "deployedAt", block.timestamp);
+ string memory finalJson = vm.serializeString(json, "contractName", contractName);
+
+ // Try to write to file (may fail due to fs permissions in scripts)
+ try vm.writeJson(finalJson, deploymentPath, string.concat(".", contractName)) {
+ console2.log("Deployment saved to:", deploymentPath);
+ } catch {
+ console2.log("Note: Could not save deployment file (use --ffi flag if needed)");
+ }
+ }
+
+ /// @notice Get network name from chain ID
+ function getNetworkName() internal view returns (string memory) {
+ uint256 chainId = block.chainid;
+ if (chainId == 421614) return "arbitrumSepolia";
+ if (chainId == 42161) return "arbitrum";
+ if (chainId == 31337) return "localhost";
+ return "unknown";
+ }
+
+ /// @notice Log deployment information
+ function logDeployment(string memory contractName, address contractAddress) internal view {
+ console2.log("\n=== Deployment Complete ===");
+ console2.log("Contract:", contractName);
+ console2.log("Address:", contractAddress);
+ console2.log("Network:", getNetworkName());
+ console2.log("Chain ID:", block.chainid);
+ console2.log("Deployer:", vm.addr(getDeployerPrivateKey()));
+ console2.log("\nNext steps:");
+ console2.log(" Verify: forge verify-contract --chain ");
+ console2.log(" Attach: Use the SDK to connect this contract to an escrow or insurance pool");
+ console2.log("===========================\n");
+ }
+}
diff --git a/script/DeploySimpleUnderwriterPolicy.s.sol b/script/DeploySimpleUnderwriterPolicy.s.sol
new file mode 100644
index 0000000..b09e94f
--- /dev/null
+++ b/script/DeploySimpleUnderwriterPolicy.s.sol
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.24;
+
+import {Deploy} from "./Deploy.s.sol";
+import {SimpleUnderwriterPolicy} from "../contracts/policies/SimpleUnderwriterPolicy.sol";
+
+contract DeploySimpleUnderwriterPolicy is Deploy {
+ function run() public override {
+ uint256 deployerPrivateKey = getDeployerPrivateKey();
+
+ vm.startBroadcast(deployerPrivateKey);
+
+ SimpleUnderwriterPolicy policy = new SimpleUnderwriterPolicy();
+
+ vm.stopBroadcast();
+
+ logDeployment("SimpleUnderwriterPolicy", address(policy));
+ saveDeployment("SimpleUnderwriterPolicy", address(policy));
+ }
+}
diff --git a/script/DeployTimeLockResolver.s.sol b/script/DeployTimeLockResolver.s.sol
new file mode 100644
index 0000000..c5f2b1b
--- /dev/null
+++ b/script/DeployTimeLockResolver.s.sol
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.24;
+
+import {Deploy} from "./Deploy.s.sol";
+import {TimeLockResolver} from "../contracts/resolvers/TimeLockResolver.sol";
+
+contract DeployTimeLockResolver is Deploy {
+ function run() public override {
+ uint256 deployerPrivateKey = getDeployerPrivateKey();
+
+ vm.startBroadcast(deployerPrivateKey);
+
+ TimeLockResolver resolver = new TimeLockResolver();
+
+ vm.stopBroadcast();
+
+ logDeployment("TimeLockResolver", address(resolver));
+ saveDeployment("TimeLockResolver", address(resolver));
+ }
+}
diff --git a/script/DeployUUPS.s.sol b/script/DeployUUPS.s.sol
new file mode 100644
index 0000000..0955356
--- /dev/null
+++ b/script/DeployUUPS.s.sol
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.24;
+
+import {Script} from "forge-std/Script.sol";
+import {console2} from "forge-std/console2.sol";
+import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";
+
+/// @title DeployUUPS
+/// @notice Deployment script for UUPS upgradeable contracts
+/// @dev Use this for deploying upgradeable resolvers or policies
+abstract contract DeployUUPS is Script {
+ /// @notice Deploy a UUPS upgradeable contract
+ /// @dev Override this function in your deployment script
+ function run() public virtual;
+
+ /// @notice Get the deployer private key from environment
+ function getDeployerPrivateKey() internal view returns (uint256) {
+ uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
+ require(deployerPrivateKey != 0, "PRIVATE_KEY not set in .env");
+ return deployerPrivateKey;
+ }
+
+ /// @notice Deploy a UUPS proxy for a contract
+ /// @param contractName Name of the implementation contract
+ /// @param initializerData Encoded initializer function call
+ /// @return proxy Address of the deployed proxy
+ function deployUUPSProxy(string memory contractName, bytes memory initializerData)
+ internal
+ returns (address proxy)
+ {
+ vm.startBroadcast(getDeployerPrivateKey());
+
+ // Deploy UUPS proxy using OpenZeppelin Foundry Upgrades
+ proxy = Upgrades.deployUUPSProxy(contractName, initializerData);
+
+ vm.stopBroadcast();
+
+ logDeployment(contractName, proxy);
+ saveDeployment(contractName, proxy);
+
+ return proxy;
+ }
+
+ /// @notice Upgrade a UUPS proxy to a new implementation
+ /// @param proxyAddress Address of the existing proxy
+ /// @param newContractName Name of the new implementation contract
+ /// @param initializerData Encoded initializer function call for the upgrade
+ function upgradeUUPSProxy(address proxyAddress, string memory newContractName, bytes memory initializerData)
+ internal
+ {
+ vm.startBroadcast(getDeployerPrivateKey());
+
+ Upgrades.upgradeProxy(proxyAddress, newContractName, initializerData);
+
+ vm.stopBroadcast();
+
+ console2.log("\n=== Upgrade Complete ===");
+ console2.log("Proxy:", proxyAddress);
+ console2.log("New Implementation:", newContractName);
+ console2.log("===========================\n");
+ }
+
+ /// @notice Save deployment to JSON file
+ /// @param contractName Name of the deployed contract
+ /// @param proxyAddress Address of the deployed proxy
+ function saveDeployment(string memory contractName, address proxyAddress) internal {
+ string memory network = getNetworkName();
+ string memory deploymentPath = string.concat("deployments/", network, ".json");
+
+ // Create deployment record
+ string memory json = "deployment";
+ vm.serializeString(json, "network", network);
+ vm.serializeAddress(json, "proxy", proxyAddress);
+ vm.serializeAddress(json, "deployer", vm.addr(getDeployerPrivateKey()));
+ vm.serializeUint(json, "deployedAt", block.timestamp);
+ vm.serializeString(json, "type", "UUPS");
+ string memory finalJson = vm.serializeString(json, "contractName", contractName);
+
+ // Try to write to file (may fail due to fs permissions in scripts)
+ try vm.writeJson(finalJson, deploymentPath, string.concat(".", contractName)) {
+ console2.log("Deployment saved to:", deploymentPath);
+ } catch {
+ console2.log("Note: Could not save deployment file (use --ffi flag if needed)");
+ }
+ }
+
+ /// @notice Get network name from chain ID
+ function getNetworkName() internal view returns (string memory) {
+ uint256 chainId = block.chainid;
+ if (chainId == 421614) return "arbitrumSepolia";
+ if (chainId == 42161) return "arbitrum";
+ if (chainId == 31337) return "localhost";
+ return "unknown";
+ }
+
+ /// @notice Log deployment information
+ function logDeployment(string memory contractName, address proxyAddress) internal view {
+ console2.log("\n=== UUPS Proxy Deployment Complete ===");
+ console2.log("Contract:", contractName);
+ console2.log("Proxy Address:", proxyAddress);
+ console2.log("Network:", getNetworkName());
+ console2.log("Chain ID:", block.chainid);
+ console2.log("Deployer:", vm.addr(getDeployerPrivateKey()));
+ console2.log("\nNext steps:");
+ console2.log(" Verify: forge verify-contract --chain ");
+ console2.log(" Attach: Use the SDK to connect this contract to an escrow or insurance pool");
+ console2.log("===========================\n");
+ }
+}
diff --git a/scripts/deploy.ts b/scripts/deploy.ts
deleted file mode 100644
index 775db89..0000000
--- a/scripts/deploy.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import hre from 'hardhat';
-import fs from 'fs';
-import path from 'path';
-
-async function main() {
- const contractName = process.env.CONTRACT_NAME;
-
- if (!contractName) {
- // List available contracts
- const resolvers = fs.readdirSync('contracts/resolvers').filter((f) => f.endsWith('.sol'));
- const policies = fs.readdirSync('contracts/policies').filter((f) => f.endsWith('.sol'));
-
- console.log('\nAvailable contracts:');
- if (resolvers.length) {
- console.log(' Resolvers:', resolvers.map((f) => f.replace('.sol', '')).join(', '));
- }
- if (policies.length) {
- console.log(' Policies:', policies.map((f) => f.replace('.sol', '')).join(', '));
- }
- if (!resolvers.length && !policies.length) {
- console.log(' (none — create a contract first)');
- }
-
- console.log('\nUsage: CONTRACT_NAME=MyResolver npx hardhat run scripts/deploy.ts --network arbitrumSepolia\n');
- return;
- }
-
- console.log(`\nDeploying ${contractName} to ${hre.network.name}...`);
-
- const [deployer] = await hre.viem.getWalletClients();
- console.log('Deployer:', deployer.account.address);
-
- const contract = await hre.viem.deployContract(contractName);
- const address = contract.address;
-
- console.log(`${contractName} deployed to: ${address}`);
-
- // Save deployment record
- const deploymentsDir = path.join(__dirname, '..', 'deployments');
- const deploymentsFile = path.join(deploymentsDir, `${hre.network.name}.json`);
-
- let deployments: Record = {};
- if (fs.existsSync(deploymentsFile)) {
- deployments = JSON.parse(fs.readFileSync(deploymentsFile, 'utf-8'));
- }
-
- const record = {
- ...deployments,
- [contractName]: {
- address,
- deployer: deployer.account.address,
- deployedAt: new Date().toISOString(),
- network: hre.network.name,
- },
- };
-
- fs.writeFileSync(deploymentsFile, JSON.stringify(record, null, 2));
- console.log(`Deployment saved to ${deploymentsFile}`);
-
- console.log('\nNext steps:');
- console.log(` Verify: npx hardhat verify --network ${hre.network.name} ${address}`);
- console.log(` Attach: Use the SDK to connect this contract to an escrow or insurance pool`);
-}
-
-main().catch((error) => {
- console.error(error);
- process.exitCode = 1;
-});
diff --git a/test/SimpleUnderwriterPolicy.t.sol b/test/SimpleUnderwriterPolicy.t.sol
new file mode 100644
index 0000000..4688bbc
--- /dev/null
+++ b/test/SimpleUnderwriterPolicy.t.sol
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.24;
+
+import {Test} from "forge-std/Test.sol";
+import {SimpleUnderwriterPolicy} from "../contracts/policies/SimpleUnderwriterPolicy.sol";
+import {IUnderwriterPolicy} from "../contracts/interfaces/IUnderwriterPolicy.sol";
+
+contract SimpleUnderwriterPolicyTest is Test {
+ SimpleUnderwriterPolicy public policy;
+
+ uint256 constant COVERAGE_ID = 1;
+ uint256 constant ESCROW_ID = 100;
+ uint64 constant BASE_RISK_SCORE = 500; // 5%
+
+ function setUp() public {
+ policy = new SimpleUnderwriterPolicy();
+
+ // Warp to a reasonable timestamp to avoid underflow
+ vm.warp(100 days);
+ }
+
+ function test_OnPolicySet() public {
+ bytes memory data = abi.encode(BASE_RISK_SCORE);
+
+ vm.expectEmit(true, false, false, true);
+ emit SimpleUnderwriterPolicy.PolicySet(COVERAGE_ID, BASE_RISK_SCORE);
+
+ policy.onPolicySet(COVERAGE_ID, data);
+
+ (uint64 storedScore, bool configured) = policy.policies(COVERAGE_ID);
+ assertEq(storedScore, BASE_RISK_SCORE);
+ assertTrue(configured);
+ }
+
+ function test_OnPolicySet_RevertsIfAlreadySet() public {
+ bytes memory data = abi.encode(BASE_RISK_SCORE);
+ policy.onPolicySet(COVERAGE_ID, data);
+
+ vm.expectRevert(SimpleUnderwriterPolicy.PolicyAlreadySet.selector);
+ policy.onPolicySet(COVERAGE_ID, data);
+ }
+
+ function test_OnPolicySet_RevertsIfScoreTooHigh() public {
+ uint64 invalidScore = 10001;
+ bytes memory data = abi.encode(invalidScore);
+
+ vm.expectRevert("Risk score must be <= 10000 bps");
+ policy.onPolicySet(COVERAGE_ID, data);
+ }
+
+ // Note: FHE operations (evaluateRisk, judge) require the CoFHE precompile
+ // which is only available on Fhenix/FHE-enabled networks, not in standard Foundry tests.
+ // These functions are tested in the TypeScript integration tests or on testnet.
+
+ function test_EvaluateRisk_RevertsIfNotConfigured() public {
+ bytes memory riskProof = "";
+
+ vm.expectRevert("Policy not configured");
+ policy.evaluateRisk(COVERAGE_ID, riskProof);
+ }
+
+ function test_Judge_RevertsIfNotConfigured() public {
+ bytes memory disputeProof = abi.encode(true, block.timestamp);
+
+ vm.expectRevert("Policy not configured");
+ policy.judge(COVERAGE_ID, disputeProof);
+ }
+
+ function test_SupportsInterface() public view {
+ bytes4 policyInterface = type(IUnderwriterPolicy).interfaceId;
+ assertTrue(policy.supportsInterface(policyInterface));
+ }
+
+ function testFuzz_OnPolicySet(uint64 riskScore) public {
+ vm.assume(riskScore <= 10000);
+
+ bytes memory data = abi.encode(riskScore);
+ policy.onPolicySet(COVERAGE_ID, data);
+
+ (uint64 storedScore, bool configured) = policy.policies(COVERAGE_ID);
+ assertEq(storedScore, riskScore);
+ assertTrue(configured);
+ }
+}
diff --git a/test/TimeLockResolver.t.sol b/test/TimeLockResolver.t.sol
new file mode 100644
index 0000000..e3af34c
--- /dev/null
+++ b/test/TimeLockResolver.t.sol
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.24;
+
+import {Test} from "forge-std/Test.sol";
+import {TimeLockResolver} from "../contracts/resolvers/TimeLockResolver.sol";
+import {IConditionResolver} from "../contracts/interfaces/IConditionResolver.sol";
+
+contract TimeLockResolverTest is Test {
+ TimeLockResolver public resolver;
+
+ uint256 constant ESCROW_ID = 1;
+ uint256 deadline;
+
+ function setUp() public {
+ resolver = new TimeLockResolver();
+ deadline = block.timestamp + 1 days;
+ }
+
+ function test_OnConditionSet() public {
+ bytes memory data = abi.encode(deadline);
+
+ vm.expectEmit(true, false, false, true);
+ emit TimeLockResolver.ConditionSet(ESCROW_ID, deadline);
+
+ resolver.onConditionSet(ESCROW_ID, data);
+
+ (uint256 storedDeadline) = resolver.configs(ESCROW_ID);
+ assertEq(storedDeadline, deadline);
+ }
+
+ function test_OnConditionSet_RevertsIfPastDeadline() public {
+ uint256 pastDeadline = block.timestamp - 1;
+ bytes memory data = abi.encode(pastDeadline);
+
+ vm.expectRevert(TimeLockResolver.InvalidDeadline.selector);
+ resolver.onConditionSet(ESCROW_ID, data);
+ }
+
+ function test_OnConditionSet_RevertsIfAlreadySet() public {
+ bytes memory data = abi.encode(deadline);
+ resolver.onConditionSet(ESCROW_ID, data);
+
+ vm.expectRevert(TimeLockResolver.ConditionAlreadySet.selector);
+ resolver.onConditionSet(ESCROW_ID, data);
+ }
+
+ function test_IsConditionMet_ReturnsFalseBeforeDeadline() public {
+ bytes memory data = abi.encode(deadline);
+ resolver.onConditionSet(ESCROW_ID, data);
+
+ assertFalse(resolver.isConditionMet(ESCROW_ID));
+ }
+
+ function test_IsConditionMet_ReturnsTrueAfterDeadline() public {
+ bytes memory data = abi.encode(deadline);
+ resolver.onConditionSet(ESCROW_ID, data);
+
+ vm.warp(deadline);
+ assertTrue(resolver.isConditionMet(ESCROW_ID));
+ }
+
+ function test_IsConditionMet_ReturnsTrueAfterDeadlinePassed() public {
+ bytes memory data = abi.encode(deadline);
+ resolver.onConditionSet(ESCROW_ID, data);
+
+ vm.warp(deadline + 1 hours);
+ assertTrue(resolver.isConditionMet(ESCROW_ID));
+ }
+
+ function test_SupportsInterface() public view {
+ bytes4 resolverInterface = type(IConditionResolver).interfaceId;
+ assertTrue(resolver.supportsInterface(resolverInterface));
+ }
+
+ function testFuzz_OnConditionSet(uint256 futureDeadline) public {
+ vm.assume(futureDeadline > block.timestamp);
+ vm.assume(futureDeadline < type(uint256).max);
+
+ bytes memory data = abi.encode(futureDeadline);
+ resolver.onConditionSet(ESCROW_ID, data);
+
+ (uint256 storedDeadline) = resolver.configs(ESCROW_ID);
+ assertEq(storedDeadline, futureDeadline);
+ }
+}
diff --git a/test/policies/.gitkeep b/test/policies/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/test/resolvers/.gitkeep b/test/resolvers/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/tsconfig.json b/tsconfig.json
deleted file mode 100644
index 0b475c3..0000000
--- a/tsconfig.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "compilerOptions": {
- "target": "es2020",
- "module": "commonjs",
- "esModuleInterop": true,
- "forceConsistentCasingInFileNames": true,
- "strict": true,
- "skipLibCheck": true,
- "resolveJsonModule": true
- },
- "include": ["./scripts", "./test", "./hardhat.config.ts"],
- "files": ["./hardhat.config.ts"]
-}