Skip to content

About

HYBOND — tokenized RWA system: upgradeable ERC20 token, the Express queued deposit/redeem gateway with KYC/compliance, asset registry, and price oracles. UUPS-upgradeable Solidity contracts (Hardhat).

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

HYBOND Contracts

Smart contracts and deployment scripts for the HYBOND system.

The repository is built on Hardhat and uses OpenZeppelin UUPS proxies for the main upgradeable contracts.

Contracts

Primary contracts in this repo:

The current PriceOracle uses:

  • dual deviation guardrails (relativeMaxDeviation and absoluteMaxDeviation)
  • staged updates via proposePrice() and confirmPrice()
  • a 1-day expiry for pending price proposals

Management Fee Accounting (Express)

Express charges a daily management fee to mgtFeeTo via updateEpoch. Fee accounting uses two counters:

  • totalMgtFeeMinted — cumulative fees ever minted. Monotonic; never decremented. Anchors historical dilution in the sharesPerToken denominator even after fee tokens are burned.
  • totalMgtFeeUnclaimed — currently live (unredeemed) fee tokens. Decremented when fee tokens move from the pending redeem queue to the final redeem queue.

Share accounting:

circulatingSupply = totalSupply − totalRedeemQueueShares − totalMgtFeeUnclaimed
sharesPerToken    = circulatingSupply / (circulatingSupply + totalMgtFeeMinted)

The ratio moves only on fee accrual and new user deposits. Fee redemptions (request → pending→final → burn) leave both circulatingSupply and sharesPerToken unchanged.

On-chain guards:

  • requestRedeem called by mgtFeeTo overrides the caller-supplied amount to totalMgtFeeUnclaimed (full live balance). Prevents fee-share provenance desync.
  • updateMgtFeeTo requires totalMgtFeeUnclaimed == 0 AND empty redeem queues.
  • requestDeposit and _calculateMintAmount both revert DrainedInstance when circulatingSupply() == 0 && totalMgtFeeMinted > 0 — a drained instance with historical fees is terminal; redeploy instead of reusing.

Operational invariants for mgtFeeTo (enforced off-chain)

The full list lives in the comment block at contracts/extension/Express.sol near the mgtFeeTo declaration. Operators must:

  1. Transfer HYBOND shares from mgtFeeTo only to the Express contract via requestRedeem.
  2. Move any non-fee shares that land on mgtFeeTo to a quarantine wallet; do not redeem them from mgtFeeTo.
  3. Never ban mgtFeeTo while it holds live fees or has in-flight fee redeems.
  4. Drain both redeem queues before rotating mgtFeeTo (also enforced on-chain).
  5. Keep mgtFeeTo unbanned for the lifetime of the pool — future updateEpoch mints would fail on a banned recipient.
  6. Keep mgtFeeTo and any fee-redeem receiver KYC-listed through settlement.
  7. Do not change convertRedeemRequestsDelay, redeemFeeRate, depositFeeRate, priceOracle, maxStalePeriod, or trimDecimals while any queue is non-empty — these values are read live at processing time and changes retroactively affect queued entries.

Requirements

  • Node.js 18+ recommended
  • npm

Install

npm install

Environment

Create a .env file for network access and verification:

PRIVATE_KEY=your_private_key
ALCHEMY_KEY=your_alchemy_api_key
ETHERSCAN_KEY=your_etherscan_api_key

Network Config

Deployment parameters are stored in:

The config format is hierarchical. Each top-level key is a contract or shared section, and that section contains its parameters.

Example:

{
  "Common": {
    "admin": "0x..."
  },
  "PriceOracle": {
    "decimals": 8,
    "relativeMaxDeviation": 500,
    "absoluteMaxDeviation": 1000,
    "initPrice": "1",
    "referencePrice": "1",
    "operator": "0x..."
  }
}

Current top-level sections:

  • Common
  • HYBOND
  • AssetRegistry
  • PriceOracle
  • Express
  • Timelock

Notes:

  • hardhat and localhost deployments use config/sepolia.json as the default config source.
  • Several address fields are set to the zero address in the sample configs. In the deploy scripts, those fields fall back to deployer for admin/operator/fee-recipient style values.
  • The Timelock section is the exception: its proposer and executor are not allowed to fall back to deployer. They must be set to the real ops multisig before deploying — the deploy script hard-fails on a zero-address proposer/executor.
  • Real deployment addresses should be filled in before deploying to testnet or mainnet.

Compile

npm run compile

Test

Run all tests:

npm test

Useful subsets:

npm run test:unit
npm run test:integration
npm run coverage

Deploy

The repo uses hardhat-deploy tags.

Full deployment is a three-phase sequence:

  1. Core contracts (hybond_all) — token, asset registry, price oracle, Express.
  2. Timelock (timelock) — deployed separately; depends on the core contracts. Not part of hybond_all, so it must be run as its own step.
  3. Governance migration — hand roles/ownership to the timelock (see Timelock Governance).
# 1. Core system
npx hardhat deploy --network sepolia --tags hybond_all

# 2. Timelock (set config/<network>.json Timelock proposer/executor first)
npm run timelock:deploy:sepolia                       # dry-run
EXECUTE=true npm run timelock:deploy:sepolia          # deploy + verify

# 3. Migrate governance to the timelock
npm run timelock:migrate:sepolia                      # dry-run plan
EXECUTE=true npm run timelock:migrate:sepolia

Individual deploy scripts:

  • deploy/00_deploy_hybond_all.ts
  • deploy/02_deploy_hybond_token.ts
  • deploy/03_deploy_asset_registry.ts
  • deploy/04_deploy_express.ts
  • deploy/05_deploy_price_oracle.ts
  • deploy/06_deploy_timelock.ts

Examples (individual tags):

npx hardhat deploy --network sepolia --tags hybond_all
npx hardhat deploy --network sepolia --tags hybond
npx hardhat deploy --network sepolia --tags asset_registry
npx hardhat deploy --network sepolia --tags express
npx hardhat deploy --network sepolia --tags price_oracle
npx hardhat deploy --network sepolia --tags timelock

Note: the hybond_all tag deploys the core system only — it does not include the timelock or the governance migration. Those are phases 2 and 3 above.

PriceOracle Deployment Notes

deploy/05_deploy_price_oracle.ts reads from the PriceOracle and Common config sections.

Initializer arguments:

(decimals, relativeMaxDeviation, absoluteMaxDeviation, initPrice, referencePrice, admin)

After deployment the script:

  • grants OPERATOR_ROLE if needed
  • does not auto-grant CONFIRMER_ROLE
  • does not auto-grant UPGRADE_ROLE

Those roles must be granted separately if your operational flow requires them.

Timelock Governance

GovernanceTimelock is a thin re-export of OpenZeppelin's TimelockController. It becomes the governance root: after migration it holds DEFAULT_ADMIN_ROLE + UPGRADE_ROLE on HYBOND/Express/PriceOracle, DEFAULT_ADMIN_ROLE on KycManager, and ownership of AssetRegistry. Critical config and all upgrades then go through a 24h announce-before-execute delay; daily operations stay instant.

Design:

  • One shared timelock per network, self-administered (admin = address(0)).
  • minDelay = 86400 (24h), uniform for config changes and upgrades.
  • Proposer = canceller = executor = the ops multisig (no separate guardian).

What the timelock covers (and does not)

Behind the 24h delay: UUPS upgrades; the Express pointer setters (updateAssetRegistry, updatePriceOracle, updateTreasury, updateRedeemAsset, setKycManager); PriceOracle deviation/reference-price setters; AssetRegistry config + ownership; and all grantRole/revokeRole (since DEFAULT_ADMIN_ROLE administers every role).

Accepted residual risk — the Express economic setters stay instant (fee rates, updateTxFeeTo, updateMgtFeeTo, updateMgtFeeRate, updateOffchainShares, deviation bps). They are deliberately not timelocked, so a compromised ops multisig can divert deposit fees or inflate management-fee tokens without the delay. The security of those paths rests on the multisig signer policy, not the timelock. This is a conscious trade-off; do not assume the timelock protects fund flows it does not.

Config

Add a Timelock section to config/<network>.json and set the real ops multisig:

{
  "Timelock": {
    "minDelay": 86400,
    "proposer": "0x<opsMultisig>",
    "executor": "0x<opsMultisig>"
  }
}

The zero-address placeholders in the sample configs are rejected by the deploy script.

Deploy

Scripts are dry-run by default (print the plan, broadcast nothing). Set EXECUTE=true to send transactions. .env decryption is wrapped via dotenvx in the npm aliases.

# 1. Deploy the timelock (dry-run first, then execute)
npm run timelock:deploy:sepolia                       # dry-run
EXECUTE=true npm run timelock:deploy:sepolia          # deploy + verify

# 2. Hand governance to the timelock (dry-run first, then execute)
npm run timelock:migrate:sepolia                      # dry-run plan
EXECUTE=true npm run timelock:migrate:sepolia

Aliases exist for sepolia, bsc-testnet, eth (mainnet), and bnb (bsc_mainnet). The lower-level hardhat-deploy tag also deploys the same contract to the same artifact: npx hardhat deploy --network sepolia --tags timelock (no dry-run gate, no .env decryption; prefer the npm alias for real rollouts).

Migration model

  • AccessControl contracts (HYBOND, Express, PriceOracle, KycManager) migrate synchronously (the deployer still holds admin): grant timelock → verify → revoke old UPGRADE_ROLE holders → renounce the deployer's DEFAULT_ADMIN_ROLE last → assert the timelock is the sole admin/upgrader. A preflight guard aborts before any mutation if a second (non-deployer) admin exists, since renounceRole is self-call-only.
  • AssetRegistry (Ownable2Step) cannot be finished synchronously — accepting ownership must run through the timelock's 24h cycle, submitted by the ops multisig. The migrate script sends transferOwnership(timelock) and prints the schedule() / execute() calldata for the multisig to submit (schedule now, execute after 24h). Governance is not fully timelocked until AssetRegistry.owner() == timelock.

The operator runbook (schedule/execute flow, queue-drain windows, compromised-key response, no-op-expiry monitoring) lives in docs/DAILY_OPERATIONS_WORKFLOW.md; the deployment/migration checklist is in docs/DEPLOYMENT_TIMELOCK.md.

Formatting

npm run format
npm run format:check

Verification

Useful commands:

npm run verify:generate-json
npm run verify:all

Notes

  • The deployment scripts are network-config driven; prefer updating config/*.json over editing hardcoded values in scripts.
  • For upgradeable contracts, do not change storage layout unless you are intentionally introducing a new deployment baseline and understand the upgrade implications.

Deployed Addresses

Deployed contract addresses for all networks (mainnet + testnet) are in deployed-addresses.md.

Audits

Independent security audit reports are in audits/.

Attribution

This project is released under the MIT License. While the MIT License does not require it, if you use this software in your own product or service, we kindly ask that you include visible attribution to OpenEden (e.g. in your documentation, UI, or credits) and link to openeden.com.

About

HYBOND — tokenized RWA system: upgradeable ERC20 token, the Express queued deposit/redeem gateway with KYC/compliance, asset registry, and price oracles. UUPS-upgradeable Solidity contracts (Hardhat).

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages