Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 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
39 changes: 17 additions & 22 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,38 +1,33 @@
name: CI
name: test

permissions: {}

on:
push:
pull_request:
workflow_dispatch:

env:
FOUNDRY_PROFILE: ci
on: [push, pull_request]

jobs:
check:
strategy:
fail-fast: true

name: Foundry project
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v3
with:
persist-credentials: false
submodules: recursive

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1

- name: Show Forge version
run: forge --version

- name: Run Forge fmt
run: forge fmt --check
with:
version: stable

- name: Run Forge build
run: forge build --sizes
run: |
forge --version
forge build --sizes
id: build

- name: Run Forge tests
run: forge test -vvv
run: |
forge test -vvv
id: test
env:
ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }}
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
## Assumptions

It is assumed that only simple, regular ERC20 tokens will be used as `gem`. In particular, the supported tokens are assumed to revert on failure (instead of returning false), not to execute any hook or apply any fee on transfer and not to execute any rebasing logic.

## Deviations from Laniakea Spec

- The spec describes the Redeemer as a separate contract from the Facility. This implementation merges both into a single contract.
- The spec requires `{principal, depositor, mintedAt}` to be stored on-chain for each NFAT. These fields are not used by any contract logic and are therefore omitted from storage.
- The spec describes burning the NFAT on full redemption. This does not fit well with deals other than single-payment-at-maturity (e.g. loans with periodic interest), as the contract has no knowledge of deal terms and burning would have to be coordinated off-chain with no on-chain purpose. Instead, NFATs are never burned and residual payments are tracked off-chain.
- The spec requires complete withdrawal only for the queue. This implementation supports partial withdrawals.
37 changes: 37 additions & 0 deletions deploy/NFATDeploy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: © 2026 Dai Foundation <www.daifoundation.org>
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

pragma solidity ^0.8.24;

import "dss-interfaces/Interfaces.sol";
import { ScriptTools } from "dss-test/ScriptTools.sol";
import { NFATFacility } from "src/NFATFacility.sol";

library NFATDeploy {

function deploy(
address deployer,
address owner,
address almProxy,
string memory name,
string memory symbol
) internal returns (address facility) {
ChainlogAbstract chainlog = ChainlogAbstract(0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F);

facility = address(new NFATFacility(chainlog.getAddress("SUSDS"), almProxy, name, symbol));
ScriptTools.switchOwner(facility, deployer, owner);
}
}
77 changes: 77 additions & 0 deletions deploy/NFATInit.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-FileCopyrightText: © 2026 Dai Foundation <www.daifoundation.org>
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

pragma solidity >=0.8.0;

import { DssInstance } from "dss-test/MCD.sol";

interface NFATFacilityLike {
function gem() external view returns (address);
function almProxy() external view returns (address);
function file(bytes32, address) external;
function setUserRole(address, uint8, bool) external;
function setRoleAction(uint8, bytes4, bool) external;
function stop() external;
function claim(address, uint256) external;
}

struct NFATConfig {
bytes32 facilityKey;
address almProxy;
address identityNetwork;
address lpha;
address[] pausers;
}

library NFATInit {

uint8 constant PAUSER = 1;
uint8 constant LPHA = 2;

function init(
DssInstance memory dss,
address facility_,
NFATConfig memory cfg
) internal {
NFATFacilityLike facility = NFATFacilityLike(facility_);

// --- Sanity checks ---

require(facility.gem() == dss.chainlog.getAddress("SUSDS"), "NFATInit/gem-mismatch");
require(facility.almProxy() == cfg.almProxy, "NFATInit/almProxy-mismatch");

// --- Configure identity network ---

facility.file("identityNetwork", cfg.identityNetwork);

// --- Configure pauser role ---

facility.setRoleAction(PAUSER, NFATFacilityLike.stop.selector, true);

for (uint256 i = 0; i < cfg.pausers.length; ++i) {
facility.setUserRole(cfg.pausers[i], PAUSER, true);
}

// --- Configure lpha role ---

facility.setRoleAction(LPHA, NFATFacilityLike.claim.selector, true);
facility.setUserRole(cfg.lpha, LPHA, true);

// --- Chainlog ---

dss.chainlog.setAddress(cfg.facilityKey, facility_);
}
}
8 changes: 7 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,11 @@
src = "src"
out = "out"
libs = ["lib"]
solc = "0.8.24"
optimizer = true
optimizer_runs = 200
evmVersion = "cancun"
verbosity = 1

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
[lint]
lint_on_build = false
Loading