{"sources":{"src/dsr/SavingsUsdd.sol":{"content":"// SPDX-License-Identifier: AGPL-3.0-or-later\n\n/// SavingsUsdd.sol -- A tokenized representation USDD in the DSR (pot)\n\n// Copyright (C) 2017, 2018, 2019 dbrock, rain, mrchico\n// Copyright (C) 2021-2022 Dai Foundation\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\npragma solidity ^0.8.17;\n\ninterface IERC1271 {\n    function isValidSignature(\n        bytes32,\n        bytes memory\n    ) external view returns (bytes4);\n}\n\ninterface VatLike {\n    function hope(address) external;\n}\n\ninterface PotLike {\n    function chi() external view returns (uint256);\n    function rho() external view returns (uint256);\n    function dsr() external view returns (uint256);\n    function drip() external returns (uint256);\n    function join(uint256) external;\n    function exit(uint256) external;\n}\n\ninterface UsddJoinLike {\n    function vat() external view returns (address);\n    function usdd() external view returns (address);\n    function join(address, uint256) external;\n    function exit(address, uint256) external;\n}\n\ninterface UsddLike {\n    function transferFrom(address, address, uint256) external returns (bool);\n    function approve(address, uint256) external returns (bool);\n}\n\ncontract SavingsUsdd {\n\n    // --- ERC20 Data ---\n    string  public constant name     = \"Savings Usdd\";\n    string  public constant symbol   = \"sUSDD\";\n    string  public constant version  = \"1\";\n    uint8   public constant decimals = 18;\n    uint256 public totalSupply;\n\n    mapping (address => uint256)                      public balanceOf;\n    mapping (address => mapping (address => uint256)) public allowance;\n    mapping (address => uint256)                      public nonces;\n\n    // --- Data ---\n    VatLike     public immutable vat;\n    UsddJoinLike public immutable usddJoin;\n    UsddLike     public immutable usdd;\n    PotLike     public immutable pot;\n\n    // --- Events ---\n    event Approval(address indexed owner, address indexed spender, uint256 value);\n    event Transfer(address indexed from, address indexed to, uint256 value);\n    event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);\n    event Withdraw(address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares);\n    event Referral(uint16 indexed referral, address indexed owner, uint256 assets, uint256 shares);\n\n    // --- EIP712 niceties ---\n    uint256 public immutable deploymentChainId;\n    bytes32 private immutable _DOMAIN_SEPARATOR;\n    bytes32 public constant PERMIT_TYPEHASH = keccak256(\"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)\");\n    \n    uint256 private constant RAY = 10 ** 27;\n\n    constructor(address _usddJoin, address _pot) {\n        usddJoin = UsddJoinLike(_usddJoin);\n        vat = VatLike(usddJoin.vat());\n        usdd = UsddLike(usddJoin.usdd());\n        pot = PotLike(_pot);\n\n        deploymentChainId = block.chainid;\n        _DOMAIN_SEPARATOR = _calculateDomainSeparator(block.chainid);\n\n        vat.hope(address(usddJoin));\n        vat.hope(address(pot));\n\n        usdd.approve(address(usddJoin), type(uint256).max);\n    }\n\n    function _calculateDomainSeparator(uint256 chainId) private view returns (bytes32) {\n        return keccak256(\n            abi.encode(\n                keccak256(\"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\"),\n                keccak256(bytes(name)),\n                keccak256(bytes(version)),\n                chainId,\n                address(this)\n            )\n        );\n    }\n\n    function DOMAIN_SEPARATOR() external view returns (bytes32) {\n        return block.chainid == deploymentChainId ? _DOMAIN_SEPARATOR : _calculateDomainSeparator(block.chainid);\n    }\n\n    function _rpow(uint256 x, uint256 n) internal pure returns (uint256 z) {\n        assembly {\n            switch x case 0 {switch n case 0 {z := RAY} default {z := 0}}\n            default {\n                switch mod(n, 2) case 0 { z := RAY } default { z := x }\n                let half := div(RAY, 2)  // for rounding.\n                for { n := div(n, 2) } n { n := div(n,2) } {\n                    let xx := mul(x, x)\n                    if iszero(eq(div(xx, x), x)) { revert(0,0) }\n                    let xxRound := add(xx, half)\n                    if lt(xxRound, xx) { revert(0,0) }\n                    x := div(xxRound, RAY)\n                    if mod(n,2) {\n                        let zx := mul(z, x)\n                        if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0,0) }\n                        let zxRound := add(zx, half)\n                        if lt(zxRound, zx) { revert(0,0) }\n                        z := div(zxRound, RAY)\n                    }\n                }\n            }\n        }\n    }\n\n    function _divup(uint256 x, uint256 y) internal pure returns (uint256 z) {\n        unchecked {\n            z = x != 0 ? ((x - 1) / y) + 1 : 0;\n        }\n    }\n\n    // --- ERC20 Mutations ---\n\n    function transfer(address to, uint256 value) external returns (bool) {\n        require(to != address(0) && to != address(this), \"SavingsUsdd/invalid-address\");\n        uint256 balance = balanceOf[msg.sender];\n        require(balance >= value, \"SavingsUsdd/insufficient-balance\");\n\n        unchecked {\n            balanceOf[msg.sender] = balance - value;\n            balanceOf[to] += value;\n        }\n\n        emit Transfer(msg.sender, to, value);\n\n        return true;\n    }\n\n    function transferFrom(address from, address to, uint256 value) external returns (bool) {\n        require(to != address(0) && to != address(this), \"SavingsUsdd/invalid-address\");\n        uint256 balance = balanceOf[from];\n        require(balance >= value, \"SavingsUsdd/insufficient-balance\");\n\n        if (from != msg.sender) {\n            uint256 allowed = allowance[from][msg.sender];\n            if (allowed != type(uint256).max) {\n                require(allowed >= value, \"SavingsUsdd/insufficient-allowance\");\n\n                unchecked {\n                    allowance[from][msg.sender] = allowed - value;\n                }\n            }\n        }\n\n        unchecked {\n            balanceOf[from] = balance - value;\n            balanceOf[to] += value;\n        }\n\n        emit Transfer(from, to, value);\n\n        return true;\n    }\n\n    function approve(address spender, uint256 value) external returns (bool) {\n        allowance[msg.sender][spender] = value;\n\n        emit Approval(msg.sender, spender, value);\n\n        return true;\n    }\n\n    function increaseAllowance(address spender, uint256 addedValue) external returns (bool) {\n        uint256 newValue = allowance[msg.sender][spender] + addedValue;\n        allowance[msg.sender][spender] = newValue;\n\n        emit Approval(msg.sender, spender, newValue);\n\n        return true;\n    }\n\n    function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool) {\n        uint256 allowed = allowance[msg.sender][spender];\n        require(allowed >= subtractedValue, \"SavingsUsdd/insufficient-allowance\");\n        unchecked{\n            allowed = allowed - subtractedValue;\n        }\n        allowance[msg.sender][spender] = allowed;\n\n        emit Approval(msg.sender, spender, allowed);\n\n        return true;\n    }\n\n    // --- Mint/Burn Internal ---\n\n    function _mint(uint256 assets, uint256 shares, address receiver) internal {\n        require(receiver != address(0) && receiver != address(this), \"SavingsUsdd/invalid-address\");\n\n        usdd.transferFrom(msg.sender, address(this), assets);\n        usddJoin.join(address(this), assets);\n        pot.join(shares);\n\n        // note: we don't need an overflow check here b/c shares totalSupply will always be <= usdd totalSupply\n        unchecked {\n            balanceOf[receiver] = balanceOf[receiver] + shares;\n            totalSupply = totalSupply + shares;\n        }\n\n        emit Deposit(msg.sender, receiver, assets, shares);\n        emit Transfer(address(0), receiver, shares);\n    }\n\n    function _burn(uint256 assets, uint256 shares, address receiver, address owner) internal {\n        uint256 balance = balanceOf[owner];\n        require(balance >= shares, \"SavingsUsdd/insufficient-balance\");\n\n        if (owner != msg.sender) {\n            uint256 allowed = allowance[owner][msg.sender];\n            if (allowed != type(uint256).max) {\n                require(allowed >= shares, \"SavingsUsdd/insufficient-allowance\");\n\n                unchecked {\n                    allowance[owner][msg.sender] = allowed - shares;\n                }\n            }\n        }\n\n        unchecked {\n            balanceOf[owner] = balance - shares; // note: we don't need overflow checks b/c require(balance >= value) and balance <= totalSupply\n            totalSupply      = totalSupply - shares;\n        }\n\n        pot.exit(shares);\n        usddJoin.exit(receiver, assets);\n\n        emit Transfer(owner, address(0), shares);\n        emit Withdraw(msg.sender, receiver, owner, assets, shares);\n    }\n\n    // --- ERC-4626 ---\n\n    function asset() external view returns (address) {\n        return address(usdd);\n    }\n\n    function totalAssets() external view returns (uint256) {\n        return convertToAssets(totalSupply);\n    }\n\n    function convertToShares(uint256 assets) public view returns (uint256) {\n        uint256 rho = pot.rho();\n        uint256 chi = (block.timestamp > rho) ? _rpow(pot.dsr(), block.timestamp - rho) * pot.chi() / RAY : pot.chi();\n        return assets * RAY / chi;\n    }\n\n    function convertToAssets(uint256 shares) public view returns (uint256) {\n        uint256 rho = pot.rho();\n        uint256 chi = (block.timestamp > rho) ? _rpow(pot.dsr(), block.timestamp - rho) * pot.chi() / RAY : pot.chi();\n        return shares * chi / RAY;\n    }\n\n    function maxDeposit(address) external pure returns (uint256) {\n        return type(uint256).max;\n    }\n\n    function previewDeposit(uint256 assets) external view returns (uint256) {\n        return convertToShares(assets);\n    }\n\n    function deposit(uint256 assets, address receiver) public returns (uint256 shares) {\n        uint256 chi = (block.timestamp > pot.rho()) ? pot.drip() : pot.chi();\n        shares = assets * RAY / chi;\n        _mint(assets, shares, receiver);\n    }\n\n    function deposit(uint256 assets, address receiver, uint16 referral) external returns (uint256 shares) {\n        shares = deposit(assets, receiver);\n        emit Referral(referral, receiver, assets, shares);\n    }\n\n    function maxMint(address) external pure returns (uint256) {\n        return type(uint256).max;\n    }\n\n    function previewMint(uint256 shares) external view returns (uint256) {\n        uint256 rho = pot.rho();\n        uint256 chi = (block.timestamp > rho) ? _rpow(pot.dsr(), block.timestamp - rho) * pot.chi() / RAY : pot.chi();\n        return _divup(shares * chi, RAY);\n    }\n\n    function mint(uint256 shares, address receiver) public returns (uint256 assets) {\n        uint256 chi = (block.timestamp > pot.rho()) ? pot.drip() : pot.chi();\n        assets = _divup(shares * chi, RAY);\n        _mint(assets, shares, receiver);\n    }\n\n    function mint(uint256 shares, address receiver, uint16 referral) external returns (uint256 assets) {\n        assets = mint(shares, receiver);\n        emit Referral(referral, receiver, assets, shares);\n    }\n\n    function maxWithdraw(address owner) external view returns (uint256) {\n        return convertToAssets(balanceOf[owner]);\n    }\n\n    function previewWithdraw(uint256 assets) external view returns (uint256) {\n        uint256 rho = pot.rho();\n        uint256 chi = (block.timestamp > rho) ? _rpow(pot.dsr(), block.timestamp - rho) * pot.chi() / RAY : pot.chi();\n        return _divup(assets * RAY, chi);\n    }\n\n    function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares) {\n        uint256 chi = (block.timestamp > pot.rho()) ? pot.drip() : pot.chi();\n        shares = _divup(assets * RAY, chi);\n        _burn(assets, shares, receiver, owner);\n    }\n\n    function maxRedeem(address owner) external view returns (uint256) {\n        return balanceOf[owner];\n    }\n\n    function previewRedeem(uint256 shares) external view returns (uint256) {\n        return convertToAssets(shares);\n    }\n\n    function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets) {\n        uint256 chi = (block.timestamp > pot.rho()) ? pot.drip() : pot.chi();\n        assets = shares * chi / RAY;\n        _burn(assets, shares, receiver, owner);\n    }\n\n    // --- Approve by signature ---\n\n    function _isValidSignature(\n        address signer,\n        bytes32 digest,\n        bytes memory signature\n    ) internal view returns (bool) {\n        if (signature.length == 65) {\n            bytes32 r;\n            bytes32 s;\n            uint8 v;\n            assembly {\n                r := mload(add(signature, 0x20))\n                s := mload(add(signature, 0x40))\n                v := byte(0, mload(add(signature, 0x60)))\n            }\n            if (signer == ecrecover(digest, v, r, s)) {\n                return true;\n            }\n        }\n\n        (bool success, bytes memory result) = signer.staticcall(\n            abi.encodeWithSelector(IERC1271.isValidSignature.selector, digest, signature)\n        );\n        return (success &&\n            result.length == 32 &&\n            abi.decode(result, (bytes4)) == IERC1271.isValidSignature.selector);\n    }\n\n    function permit(\n        address owner,\n        address spender,\n        uint256 value,\n        uint256 deadline,\n        bytes memory signature\n    ) public {\n        require(block.timestamp <= deadline, \"SavingsUsdd/permit-expired\");\n        require(owner != address(0), \"SavingsUsdd/invalid-owner\");\n\n        uint256 nonce;\n        unchecked { nonce = nonces[owner]++; }\n\n        bytes32 digest =\n            keccak256(abi.encodePacked(\n                \"\\x19\\x01\",\n                block.chainid == deploymentChainId ? _DOMAIN_SEPARATOR : _calculateDomainSeparator(block.chainid),\n                keccak256(abi.encode(\n                    PERMIT_TYPEHASH,\n                    owner,\n                    spender,\n                    value,\n                    nonce,\n                    deadline\n                ))\n            ));\n\n        require(_isValidSignature(owner, digest, signature), \"SavingsUsdd/invalid-permit\");\n\n        allowance[owner][spender] = value;\n        emit Approval(owner, spender, value);\n    }\n\n    function permit(\n        address owner,\n        address spender,\n        uint256 value,\n        uint256 deadline,\n        uint8 v,\n        bytes32 r,\n        bytes32 s\n    ) external {\n        permit(owner, spender, value, deadline, abi.encodePacked(r, s, v));\n    }\n\n}\n"}},"compilation":{"language":"Solidity","compiler":"solc","compilerVersion":"0.8.30+commit.73712a01","compilerSettings":{"viaIR":false,"metadata":{"appendCBOR":true,"bytecodeHash":"ipfs","useLiteralContent":false},"optimizer":{"runs":200,"enabled":true},"evmVersion":"cancun","remappings":["ds-test/=lib/ds-auth/lib/ds-test/src/","ds-thing/=lib/ds-value/lib/ds-thing/src/","ds-value/=lib/ds-value/src/","forge-std/=lib/forge-std/src/","psm/=lib/psm/src/","ds-token/=lib/ds-token/src/","ds-auth/=lib/ds-auth/src/","erc4626-tests/=lib/erc4626-tests/","ds-math/=lib/ds-token/lib/ds-math/src/","ds-note/=lib/ds-value/lib/ds-thing/lib/ds-note/src/","usddv2/=lib/psm/lib/usddv2/src/"]},"name":"SavingsUsdd","fullyQualifiedName":"src/dsr/SavingsUsdd.sol:SavingsUsdd"},"matchId":"28949308","creationMatch":"exact_match","runtimeMatch":"exact_match","verifiedAt":"2026-05-15T11:44:38Z","match":"exact_match","chainId":"56","address":"0x8bA9dA757d1D66c58b1ae7e2ED6c04087348A82d"}