{"file_path":"contracts/Savings.sol","creation_status":"success","source_code":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {IDecentralizedEURO} from \"./interface/IDecentralizedEURO.sol\";\nimport {IReserve} from \"./interface/IReserve.sol\";\nimport {Leadrate} from \"./Leadrate.sol\";\n\n/**\n * @title Savings\n *\n * Module to enable savings based on a Leadrate (\"Leitzins\") module.\n *\n * As the interest rate changes, the speed at which 'ticks' are accumulated is\n * adjusted. The ticks counter serves as the basis for calculating the interest\n * due for the individual accounts.\n */\ncontract Savings is Leadrate {\n    IERC20 public immutable deuro;\n\n    mapping(address => Account) public savings;\n    mapping(address => bool) public nonCompounding;\n    mapping(address => uint192) public claimableInterest;\n\n    struct Account {\n        uint192 saved;\n        uint64 ticks;\n    }\n\n    event Saved(address indexed account, uint192 amount);\n    event InterestCollected(address indexed account, uint256 interest, bool compounded);\n    event Withdrawn(address indexed account, uint192 amount);\n    event InterestClaimed(address indexed account, uint192 amount);\n\n    // The module is considered disabled if the interest is zero or about to become zero within three days.\n    error ModuleDisabled();\n\n    constructor(IDecentralizedEURO deuro_, uint24 initialRatePPM) Leadrate(IReserve(deuro_.reserve()), initialRatePPM) {\n        deuro = IERC20(deuro_);\n    }\n\n    /**\n     * Shortcut for refreshBalance(msg.sender)\n     */\n    function refreshMyBalance() public returns (uint192) {\n        return refreshBalance(msg.sender);\n    }\n\n    /**\n     * Collects the accrued interest and adds it to the account.\n     *\n     * It can be beneficial to do so every now and then in order to start collecting\n     * interest on the accrued interest.\n     */\n    function refreshBalance(address owner) public returns (uint192) {\n        return refresh(owner).saved;\n    }\n\n    function refresh(address accountOwner) internal returns (Account storage) {\n        Account storage account = savings[accountOwner];\n        uint64 ticks = currentTicks();\n        if (ticks > account.ticks) {\n            uint192 earnedInterest = calculateInterest(account, ticks);\n            if (earnedInterest > 0) {\n                (IDecentralizedEURO(address(deuro))).distributeProfits(address(this), earnedInterest);\n                bool compounded = !nonCompounding[accountOwner];\n                if (compounded) {\n                    account.saved += earnedInterest;\n                } else {\n                    claimableInterest[accountOwner] += earnedInterest;\n                }\n                emit InterestCollected(accountOwner, earnedInterest, compounded);\n            }\n            account.ticks = ticks;\n        }\n        return account;\n    }\n\n    function accruedInterest(address accountOwner) public view returns (uint192) {\n        return accruedInterest(accountOwner, block.timestamp);\n    }\n\n    function accruedInterest(address accountOwner, uint256 timestamp) public view returns (uint192) {\n        Account memory account = savings[accountOwner];\n        return calculateInterest(account, ticks(timestamp));\n    }\n\n    function calculateInterest(Account memory account, uint64 ticks) public view returns (uint192) {\n        if (ticks <= account.ticks || account.ticks == 0) {\n            return 0;\n        } else {\n            uint192 earnedInterest = uint192((uint256(ticks - account.ticks) * account.saved) / 1_000_000 / 365 days);\n            uint256 equity = IDecentralizedEURO(address(deuro)).equity();\n            if (earnedInterest > equity) {\n                return uint192(equity); // safe conversion as equity is smaller than uint192 earnedInterest\n            } else {\n                return earnedInterest;\n            }\n        }\n    }\n\n    /**\n     * Save 'amount'.\n     */\n    function save(uint192 amount) public {\n        save(msg.sender, amount);\n    }\n\n    /**\n     * Save 'amount' and set the compounding preference.\n     * Pending interest is settled under the OLD mode before applying the new preference.\n     */\n    function save(uint192 amount, bool compound) public {\n        save(msg.sender, amount);\n        nonCompounding[msg.sender] = !compound;\n    }\n\n    function adjust(uint192 targetAmount) public {\n        Account storage balance = refresh(msg.sender);\n        if (balance.saved < targetAmount) {\n            save(targetAmount - balance.saved);\n        } else if (balance.saved > targetAmount) {\n            withdraw(msg.sender, balance.saved - targetAmount);\n        }\n    }\n\n    /**\n     * Send 'amount' to the account of the provided owner.\n     */\n    function save(address owner, uint192 amount) public {\n        if (currentRatePPM == 0) revert ModuleDisabled();\n        if (nextRatePPM == 0 && (nextChange <= block.timestamp)) revert ModuleDisabled();\n        Account storage balance = refresh(owner);\n        deuro.transferFrom(msg.sender, address(this), amount);\n        assert(balance.ticks >= currentTicks()); // @dev: should not differ, since there is no shift of interests\n        balance.saved += amount;\n        emit Saved(owner, amount);\n    }\n\n    /**\n     * Withdraw up to 'amount' to the target address.\n     * When trying to withdraw more than available, all that is available is withdrawn.\n     * Returns the actually transferred amount.\n     */\n    function withdraw(address target, uint192 amount) public returns (uint256) {\n        Account storage account = refresh(msg.sender);\n        if (amount >= account.saved) {\n            amount = account.saved;\n            delete savings[msg.sender];\n        } else {\n            account.saved -= amount;\n        }\n        deuro.transfer(target, amount);\n        emit Withdrawn(msg.sender, amount);\n        return amount;\n    }\n\n    function claimInterest(address target) public returns (uint192) {\n        refresh(msg.sender);\n        uint192 interest = claimableInterest[msg.sender];\n        if (interest > 0) {\n            claimableInterest[msg.sender] = 0;\n            deuro.transfer(target, interest);\n            emit InterestClaimed(msg.sender, interest);\n        }\n        return interest;\n    }\n}\n","deployed_bytecode":"0x608060405234801561001057600080fd5b506004361061014d5760003560e01c806385bd12d1116100c3578063b6f83c171161007c578063b6f83c1714610371578063b77cd1c71461039f578063bdc65152146103b2578063c1983610146103c5578063cdb95c3b146103d8578063fd449c141461040b57600080fd5b806385bd12d11461030157806388d742c21461030957806391a0ac6a1461031c578063a696399d14610343578063a776f78c14610356578063b079f1631461036957600080fd5b80634e65037e116101155780634e65037e146102285780634f24d20a14610230578063534cb30d1461027157806377267ec31461029c5780637915ce20146102af57806382b8eaf5146102c257600080fd5b806306a7b376146101525780631791dc5e1461017a5780631de252e41461019b5780631f7cdd5f146101b05780632e4b20ab14610212575b600080fd5b6000546101619062ffffff1681565b60405162ffffff90911681526020015b60405180910390f35b61018d610188366004611060565b61041e565b604051908152602001610171565b6101ae6101a9366004611093565b610589565b005b6101eb6101be366004611123565b6001602052600090815260409020546001600160c01b03811690600160c01b90046001600160401b031682565b604080516001600160c01b0390931683526001600160401b03909116602083015201610171565b600054610161906301000000900462ffffff1681565b6101ae6106b1565b61025961023e366004611123565b6003602052600090815260409020546001600160c01b031681565b6040516001600160c01b039091168152602001610171565b61028461027f366004611145565b61081a565b6040516001600160401b039091168152602001610171565b6102596102aa366004611123565b610864565b6102596102bd366004611175565b610870565b6102e97f000000000000000000000000ba3f535bbcccca2a154b573ca6c5a49baae0a3ea81565b6040516001600160a01b039091168152602001610171565b6102596109a9565b610259610317366004611123565b6109b9565b6102e97f000000000000000000000000c71104001a3ccda1bef1177d765831bd1bfe8ee681565b6102596103513660046111f5565b610ad7565b6101ae61036436600461122d565b610b33565b610284610b5d565b60005461038990600160301b900464ffffffffff1681565b60405164ffffffffff9091168152602001610171565b6102596103ad366004611123565b610b68565b6101ae6103c0366004611264565b610b83565b6101ae6103d3366004611060565b610bf9565b6103fb6103e6366004611123565b60026020526000908152604090205460ff1681565b6040519015158152602001610171565b6101ae610419366004611264565b610dde565b60008061042a33610deb565b80549091506001600160c01b0390811690841610610464578054336000908152600160205260408120556001600160c01b031692506104a6565b8054839082906000906104819084906001600160c01b0316611295565b92506101000a8154816001600160c01b0302191690836001600160c01b031602179055505b60405163a9059cbb60e01b81526001600160a01b037f000000000000000000000000ba3f535bbcccca2a154b573ca6c5a49baae0a3ea169063a9059cbb906104f490879087906004016112b4565b6020604051808303816000875af1158015610513573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061053791906112d6565b506040516001600160c01b038416815233907f47cf194f5e559cca0413017d38814a7843cc6f3052bc43c8085938774ae581519060200160405180910390a250506001600160c01b0381165b92915050565b60405163352e3a8360e01b81526001600160a01b037f000000000000000000000000c71104001a3ccda1bef1177d765831bd1bfe8ee6169063352e3a83906105d9903390869086906004016112f3565b60006040518083038186803b1580156105f157600080fd5b505afa158015610605573d6000803e3d6000fd5b50506000805465ffffff0000001916630100000062ffffff8816021790555061063390504262093a8061134f565b600080546affffffffff0000000000001916600160301b64ffffffffff93841681029190911791829055604080513381526301000000840462ffffff16602082015291909204909216908201527e4964f2e48bd8a460fb41883098593956d335f36fdded4ca9cbac9252d72b2f9060600160405180910390a1505050565b60005462ffffff6301000000820481169116036106e15760405163147fde5f60e31b815260040160405180910390fd5b600054429064ffffffffff600160301b90910481169082161015610718576040516301b3782160e21b815260040160405180910390fd5b60005462ffffff81169061073a90600160581b900464ffffffffff1683611362565b64ffffffffff1661074b919061137f565b6000805460109061076d908490600160801b90046001600160401b03166113a1565b82546101009290920a6001600160401b038181021990931691909216919091021790555060008054630100000064ffffffffff60581b198216600160581b64ffffffffff8616029081179190910462ffffff166fffffffffff0000000000000000ffffff1990921662ffffff19919091161781179091556040519081527fd76dfbd4c35cffe2a846b6488bc677c511aa4337e1551f3a360427ac7a78de7b9060200160405180910390a150565b6000805462ffffff81169061083d90600160581b900464ffffffffff16846113c0565b610847919061137f565b6000546105839190600160801b90046001600160401b03166113a1565b60006105838242610ad7565b600082602001516001600160401b0316826001600160401b03161115806108a2575060208301516001600160401b0316155b156108af57506000610583565b60006301e13380620f424085600001516001600160c01b03168660200151866108d891906113c0565b6001600160401b03166108eb91906113df565b6108f591906113f6565b6108ff91906113f6565b905060007f000000000000000000000000ba3f535bbcccca2a154b573ca6c5a49baae0a3ea6001600160a01b03166391a0ac6a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610961573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109859190611418565b905080826001600160c01b031611156109a15791506105839050565b509050610583565b60006109b433610b68565b905090565b60006109c433610deb565b50336000908152600360205260409020546001600160c01b0316801561058357336000908152600360205260409081902080546001600160c01b03191690555163a9059cbb60e01b81527f000000000000000000000000ba3f535bbcccca2a154b573ca6c5a49baae0a3ea6001600160a01b03169063a9059cbb90610a4f90869085906004016112b4565b6020604051808303816000875af1158015610a6e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a9291906112d6565b506040516001600160c01b038216815233907f050a77164c9681c5fd7677ea2acbb7128099ee18d3fc72643d25834fda50ecc69060200160405180910390a292915050565b6001600160a01b03821660009081526001602090815260408083208151808301909252546001600160c01b0381168252600160c01b90046001600160401b031691810191909152610b2b816102bd8561081a565b949350505050565b610b3d3383610bf9565b336000908152600260205260409020805460ff1916911591909117905550565b60006109b44261081a565b6000610b7382610deb565b546001600160c01b031692915050565b6000610b8e33610deb565b80549091506001600160c01b0380841691161015610bc3578054610bbf90610419906001600160c01b031684611295565b5050565b80546001600160c01b0380841691161115610bbf578054610bf49033906101889085906001600160c01b0316611295565b505050565b6000805462ffffff169003610c2157604051630dbfe5fd60e31b815260040160405180910390fd5b6000546301000000900462ffffff16158015610c4e575060005442600160301b90910464ffffffffff1611155b15610c6c57604051630dbfe5fd60e31b815260040160405180910390fd5b6000610c7783610deb565b6040516323b872dd60e01b81523360048201523060248201526001600160c01b03841660448201529091507f000000000000000000000000ba3f535bbcccca2a154b573ca6c5a49baae0a3ea6001600160a01b0316906323b872dd906064016020604051808303816000875af1158015610cf5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d1991906112d6565b50610d22610b5d565b81546001600160401b03918216600160c01b9091049091161015610d4857610d48611431565b805482908290600090610d659084906001600160c01b0316611447565b92506101000a8154816001600160c01b0302191690836001600160c01b03160217905550826001600160a01b03167ff195ce54b48d5147da31c1fc525c8828b8836088b505a329e5de2b35da6731e283604051610dd191906001600160c01b0391909116815260200190565b60405180910390a2505050565b610de83382610bf9565b50565b6001600160a01b038116600090815260016020526040812081610e0c610b5d565b82549091506001600160401b03600160c01b90910481169082161115611026576040805180820190915282546001600160c01b0381168252600160c01b90046001600160401b03166020820152600090610e669083610870565b90506001600160c01b03811615611006576040516308b8bb4b60e01b81526001600160a01b037f000000000000000000000000ba3f535bbcccca2a154b573ca6c5a49baae0a3ea16906308b8bb4b90610ec590309085906004016112b4565b600060405180830381600087803b158015610edf57600080fd5b505af1158015610ef3573d6000803e3d6000fd5b505050506001600160a01b03851660009081526002602052604090205460ff16158015610f6057835482908590600090610f379084906001600160c01b0316611447565b92506101000a8154816001600160c01b0302191690836001600160c01b03160217905550610fb6565b6001600160a01b03861660009081526003602052604081208054849290610f919084906001600160c01b0316611447565b92506101000a8154816001600160c01b0302191690836001600160c01b031602179055505b604080516001600160c01b038416815282151560208201526001600160a01b038816917fff644be0d778ef4fcd23923a69e60a49a74abdde416ca21a85f444b9d9929529910160405180910390a2505b5081546001600160c01b0316600160c01b6001600160401b038316021782555b5092915050565b80356001600160a01b038116811461104457600080fd5b919050565b80356001600160c01b038116811461104457600080fd5b6000806040838503121561107357600080fd5b61107c8361102d565b915061108a60208401611049565b90509250929050565b6000806000604084860312156110a857600080fd5b833562ffffff811681146110bb57600080fd5b925060208401356001600160401b038111156110d657600080fd5b8401601f810186136110e757600080fd5b80356001600160401b038111156110fd57600080fd5b8660208260051b840101111561111257600080fd5b939660209190910195509293505050565b60006020828403121561113557600080fd5b61113e8261102d565b9392505050565b60006020828403121561115757600080fd5b5035919050565b80356001600160401b038116811461104457600080fd5b600080828403606081121561118957600080fd5b604081121561119757600080fd5b50604051604081018181106001600160401b03821117156111c857634e487b7160e01b600052604160045260246000fd5b6040526111d484611049565b81526111e26020850161115e565b6020820152915061108a6040840161115e565b6000806040838503121561120857600080fd5b6112118361102d565b946020939093013593505050565b8015158114610de857600080fd5b6000806040838503121561124057600080fd5b61124983611049565b915060208301356112598161121f565b809150509250929050565b60006020828403121561127657600080fd5b61113e82611049565b634e487b7160e01b600052601160045260246000fd5b6001600160c01b0382811682821603908111156105835761058361127f565b6001600160a01b039290921682526001600160c01b0316602082015260400190565b6000602082840312156112e857600080fd5b815161113e8161121f565b6001600160a01b0384168152604060208201819052810182905260008360608301825b85811015611344576001600160a01b0361132f8461102d565b16825260209283019290910190600101611316565b509695505050505050565b808201808211156105835761058361127f565b64ffffffffff82811682821603908111156105835761058361127f565b6001600160401b0381811683821602908116908181146110265761102661127f565b6001600160401b0381811683821601908111156105835761058361127f565b6001600160401b0382811682821603908111156105835761058361127f565b80820281158282048414176105835761058361127f565b60008261141357634e487b7160e01b600052601260045260246000fd5b500490565b60006020828403121561142a57600080fd5b5051919050565b634e487b7160e01b600052600160045260246000fd5b6001600160c01b0381811683821601908111156105835761058361127f56fea2646970667358221220486b2bcefc3af8318fa67e922b893fa617fc6d2681a0351f019706ceac09cc7164736f6c634300081a0033","optimization_enabled":true,"verified_twin_address_hash":null,"is_verified":true,"compiler_settings":{"evmVersion":"paris","libraries":{},"metadata":{"bytecodeHash":"ipfs","useLiteralContent":true},"optimizer":{"enabled":true,"runs":200},"outputSelection":{"*":{"":["*"],"*":["*"]}}},"optimization_runs":200,"sourcify_repo_url":null,"decoded_constructor_args":[["0xbA3f535bbCcCcA2A154b573Ca6c5A49BAAE0a3ea",{"internalType":"contract IDecentralizedEURO","name":"deuro_","type":"address"}],["80000",{"internalType":"uint24","name":"initialRatePPM","type":"uint24"}]],"compiler_version":"v0.8.26+commit.8a97fa7a","is_verified_via_verifier_alliance":false,"verified_at":"2026-03-23T16:08:12.609060Z","implementations":[],"proxy_type":null,"external_libraries":[],"creation_bytecode":"0x60c060405234801561001057600080fd5b506040516116d13803806116d183398101604081905261002f91610180565b816001600160a01b031663cd3293de6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561006d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061009191906101c2565b6001600160a01b03811660805260008054600160801b600160c01b03196b010000000000000000000000660100000000000064ffffffffff42811682026affffffffff000000ffffff1962ffffff8a1663010000008102919091166001600160581b031990971696909617861717918204169190910291909116600160581b600160c01b0319919091161790915560405190815282907fd76dfbd4c35cffe2a846b6488bc677c511aa4337e1551f3a360427ac7a78de7b9060200160405180910390a15050506001600160a01b031660a0526101e6565b6001600160a01b038116811461017d57600080fd5b50565b6000806040838503121561019357600080fd5b825161019e81610168565b602084015190925062ffffff811681146101b757600080fd5b809150509250929050565b6000602082840312156101d457600080fd5b81516101df81610168565b9392505050565b60805160a05161149c610235600039600081816102c7015281816104bd0152818161090501528181610a1001528181610ca40152610e8e01526000818161032101526105a0015261149c6000f3fe608060405234801561001057600080fd5b506004361061014d5760003560e01c806385bd12d1116100c3578063b6f83c171161007c578063b6f83c1714610371578063b77cd1c71461039f578063bdc65152146103b2578063c1983610146103c5578063cdb95c3b146103d8578063fd449c141461040b57600080fd5b806385bd12d11461030157806388d742c21461030957806391a0ac6a1461031c578063a696399d14610343578063a776f78c14610356578063b079f1631461036957600080fd5b80634e65037e116101155780634e65037e146102285780634f24d20a14610230578063534cb30d1461027157806377267ec31461029c5780637915ce20146102af57806382b8eaf5146102c257600080fd5b806306a7b376146101525780631791dc5e1461017a5780631de252e41461019b5780631f7cdd5f146101b05780632e4b20ab14610212575b600080fd5b6000546101619062ffffff1681565b60405162ffffff90911681526020015b60405180910390f35b61018d610188366004611060565b61041e565b604051908152602001610171565b6101ae6101a9366004611093565b610589565b005b6101eb6101be366004611123565b6001602052600090815260409020546001600160c01b03811690600160c01b90046001600160401b031682565b604080516001600160c01b0390931683526001600160401b03909116602083015201610171565b600054610161906301000000900462ffffff1681565b6101ae6106b1565b61025961023e366004611123565b6003602052600090815260409020546001600160c01b031681565b6040516001600160c01b039091168152602001610171565b61028461027f366004611145565b61081a565b6040516001600160401b039091168152602001610171565b6102596102aa366004611123565b610864565b6102596102bd366004611175565b610870565b6102e97f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610171565b6102596109a9565b610259610317366004611123565b6109b9565b6102e97f000000000000000000000000000000000000000000000000000000000000000081565b6102596103513660046111f5565b610ad7565b6101ae61036436600461122d565b610b33565b610284610b5d565b60005461038990600160301b900464ffffffffff1681565b60405164ffffffffff9091168152602001610171565b6102596103ad366004611123565b610b68565b6101ae6103c0366004611264565b610b83565b6101ae6103d3366004611060565b610bf9565b6103fb6103e6366004611123565b60026020526000908152604090205460ff1681565b6040519015158152602001610171565b6101ae610419366004611264565b610dde565b60008061042a33610deb565b80549091506001600160c01b0390811690841610610464578054336000908152600160205260408120556001600160c01b031692506104a6565b8054839082906000906104819084906001600160c01b0316611295565b92506101000a8154816001600160c01b0302191690836001600160c01b031602179055505b60405163a9059cbb60e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906104f490879087906004016112b4565b6020604051808303816000875af1158015610513573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061053791906112d6565b506040516001600160c01b038416815233907f47cf194f5e559cca0413017d38814a7843cc6f3052bc43c8085938774ae581519060200160405180910390a250506001600160c01b0381165b92915050565b60405163352e3a8360e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063352e3a83906105d9903390869086906004016112f3565b60006040518083038186803b1580156105f157600080fd5b505afa158015610605573d6000803e3d6000fd5b50506000805465ffffff0000001916630100000062ffffff8816021790555061063390504262093a8061134f565b600080546affffffffff0000000000001916600160301b64ffffffffff93841681029190911791829055604080513381526301000000840462ffffff16602082015291909204909216908201527e4964f2e48bd8a460fb41883098593956d335f36fdded4ca9cbac9252d72b2f9060600160405180910390a1505050565b60005462ffffff6301000000820481169116036106e15760405163147fde5f60e31b815260040160405180910390fd5b600054429064ffffffffff600160301b90910481169082161015610718576040516301b3782160e21b815260040160405180910390fd5b60005462ffffff81169061073a90600160581b900464ffffffffff1683611362565b64ffffffffff1661074b919061137f565b6000805460109061076d908490600160801b90046001600160401b03166113a1565b82546101009290920a6001600160401b038181021990931691909216919091021790555060008054630100000064ffffffffff60581b198216600160581b64ffffffffff8616029081179190910462ffffff166fffffffffff0000000000000000ffffff1990921662ffffff19919091161781179091556040519081527fd76dfbd4c35cffe2a846b6488bc677c511aa4337e1551f3a360427ac7a78de7b9060200160405180910390a150565b6000805462ffffff81169061083d90600160581b900464ffffffffff16846113c0565b610847919061137f565b6000546105839190600160801b90046001600160401b03166113a1565b60006105838242610ad7565b600082602001516001600160401b0316826001600160401b03161115806108a2575060208301516001600160401b0316155b156108af57506000610583565b60006301e13380620f424085600001516001600160c01b03168660200151866108d891906113c0565b6001600160401b03166108eb91906113df565b6108f591906113f6565b6108ff91906113f6565b905060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166391a0ac6a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610961573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109859190611418565b905080826001600160c01b031611156109a15791506105839050565b509050610583565b60006109b433610b68565b905090565b60006109c433610deb565b50336000908152600360205260409020546001600160c01b0316801561058357336000908152600360205260409081902080546001600160c01b03191690555163a9059cbb60e01b81527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063a9059cbb90610a4f90869085906004016112b4565b6020604051808303816000875af1158015610a6e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a9291906112d6565b506040516001600160c01b038216815233907f050a77164c9681c5fd7677ea2acbb7128099ee18d3fc72643d25834fda50ecc69060200160405180910390a292915050565b6001600160a01b03821660009081526001602090815260408083208151808301909252546001600160c01b0381168252600160c01b90046001600160401b031691810191909152610b2b816102bd8561081a565b949350505050565b610b3d3383610bf9565b336000908152600260205260409020805460ff1916911591909117905550565b60006109b44261081a565b6000610b7382610deb565b546001600160c01b031692915050565b6000610b8e33610deb565b80549091506001600160c01b0380841691161015610bc3578054610bbf90610419906001600160c01b031684611295565b5050565b80546001600160c01b0380841691161115610bbf578054610bf49033906101889085906001600160c01b0316611295565b505050565b6000805462ffffff169003610c2157604051630dbfe5fd60e31b815260040160405180910390fd5b6000546301000000900462ffffff16158015610c4e575060005442600160301b90910464ffffffffff1611155b15610c6c57604051630dbfe5fd60e31b815260040160405180910390fd5b6000610c7783610deb565b6040516323b872dd60e01b81523360048201523060248201526001600160c01b03841660448201529091507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906323b872dd906064016020604051808303816000875af1158015610cf5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d1991906112d6565b50610d22610b5d565b81546001600160401b03918216600160c01b9091049091161015610d4857610d48611431565b805482908290600090610d659084906001600160c01b0316611447565b92506101000a8154816001600160c01b0302191690836001600160c01b03160217905550826001600160a01b03167ff195ce54b48d5147da31c1fc525c8828b8836088b505a329e5de2b35da6731e283604051610dd191906001600160c01b0391909116815260200190565b60405180910390a2505050565b610de83382610bf9565b50565b6001600160a01b038116600090815260016020526040812081610e0c610b5d565b82549091506001600160401b03600160c01b90910481169082161115611026576040805180820190915282546001600160c01b0381168252600160c01b90046001600160401b03166020820152600090610e669083610870565b90506001600160c01b03811615611006576040516308b8bb4b60e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906308b8bb4b90610ec590309085906004016112b4565b600060405180830381600087803b158015610edf57600080fd5b505af1158015610ef3573d6000803e3d6000fd5b505050506001600160a01b03851660009081526002602052604090205460ff16158015610f6057835482908590600090610f379084906001600160c01b0316611447565b92506101000a8154816001600160c01b0302191690836001600160c01b03160217905550610fb6565b6001600160a01b03861660009081526003602052604081208054849290610f919084906001600160c01b0316611447565b92506101000a8154816001600160c01b0302191690836001600160c01b031602179055505b604080516001600160c01b038416815282151560208201526001600160a01b038816917fff644be0d778ef4fcd23923a69e60a49a74abdde416ca21a85f444b9d9929529910160405180910390a2505b5081546001600160c01b0316600160c01b6001600160401b038316021782555b5092915050565b80356001600160a01b038116811461104457600080fd5b919050565b80356001600160c01b038116811461104457600080fd5b6000806040838503121561107357600080fd5b61107c8361102d565b915061108a60208401611049565b90509250929050565b6000806000604084860312156110a857600080fd5b833562ffffff811681146110bb57600080fd5b925060208401356001600160401b038111156110d657600080fd5b8401601f810186136110e757600080fd5b80356001600160401b038111156110fd57600080fd5b8660208260051b840101111561111257600080fd5b939660209190910195509293505050565b60006020828403121561113557600080fd5b61113e8261102d565b9392505050565b60006020828403121561115757600080fd5b5035919050565b80356001600160401b038116811461104457600080fd5b600080828403606081121561118957600080fd5b604081121561119757600080fd5b50604051604081018181106001600160401b03821117156111c857634e487b7160e01b600052604160045260246000fd5b6040526111d484611049565b81526111e26020850161115e565b6020820152915061108a6040840161115e565b6000806040838503121561120857600080fd5b6112118361102d565b946020939093013593505050565b8015158114610de857600080fd5b6000806040838503121561124057600080fd5b61124983611049565b915060208301356112598161121f565b809150509250929050565b60006020828403121561127657600080fd5b61113e82611049565b634e487b7160e01b600052601160045260246000fd5b6001600160c01b0382811682821603908111156105835761058361127f565b6001600160a01b039290921682526001600160c01b0316602082015260400190565b6000602082840312156112e857600080fd5b815161113e8161121f565b6001600160a01b0384168152604060208201819052810182905260008360608301825b85811015611344576001600160a01b0361132f8461102d565b16825260209283019290910190600101611316565b509695505050505050565b808201808211156105835761058361127f565b64ffffffffff82811682821603908111156105835761058361127f565b6001600160401b0381811683821602908116908181146110265761102661127f565b6001600160401b0381811683821601908111156105835761058361127f565b6001600160401b0382811682821603908111156105835761058361127f565b80820281158282048414176105835761058361127f565b60008261141357634e487b7160e01b600052601260045260246000fd5b500490565b60006020828403121561142a57600080fd5b5051919050565b634e487b7160e01b600052600160045260246000fd5b6001600160c01b0381811683821601908111156105835761058361127f56fea2646970667358221220486b2bcefc3af8318fa67e922b893fa617fc6d2681a0351f019706ceac09cc7164736f6c634300081a0033000000000000000000000000ba3f535bbcccca2a154b573ca6c5a49baae0a3ea0000000000000000000000000000000000000000000000000000000000013880","name":"Savings","is_blueprint":false,"license_type":"none","is_fully_verified":true,"is_verified_via_eth_bytecode_db":false,"language":"solidity","evm_version":"paris","can_be_visualized_via_sol2uml":true,"is_verified_via_sourcify":false,"additional_sources":[{"file_path":"@openzeppelin/contracts/token/ERC20/IERC20.sol","source_code":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Interface of the ERC-20 standard as defined in the ERC.\n */\ninterface IERC20 {\n    /**\n     * @dev Emitted when `value` tokens are moved from one account (`from`) to\n     * another (`to`).\n     *\n     * Note that `value` may be zero.\n     */\n    event Transfer(address indexed from, address indexed to, uint256 value);\n\n    /**\n     * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n     * a call to {approve}. `value` is the new allowance.\n     */\n    event Approval(address indexed owner, address indexed spender, uint256 value);\n\n    /**\n     * @dev Returns the value of tokens in existence.\n     */\n    function totalSupply() external view returns (uint256);\n\n    /**\n     * @dev Returns the value of tokens owned by `account`.\n     */\n    function balanceOf(address account) external view returns (uint256);\n\n    /**\n     * @dev Moves a `value` amount of tokens from the caller's account to `to`.\n     *\n     * Returns a boolean value indicating whether the operation succeeded.\n     *\n     * Emits a {Transfer} event.\n     */\n    function transfer(address to, uint256 value) external returns (bool);\n\n    /**\n     * @dev Returns the remaining number of tokens that `spender` will be\n     * allowed to spend on behalf of `owner` through {transferFrom}. This is\n     * zero by default.\n     *\n     * This value changes when {approve} or {transferFrom} are called.\n     */\n    function allowance(address owner, address spender) external view returns (uint256);\n\n    /**\n     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n     * caller's tokens.\n     *\n     * Returns a boolean value indicating whether the operation succeeded.\n     *\n     * IMPORTANT: Beware that changing an allowance with this method brings the risk\n     * that someone may use both the old and the new allowance by unfortunate\n     * transaction ordering. One possible solution to mitigate this race\n     * condition is to first reduce the spender's allowance to 0 and set the\n     * desired value afterwards:\n     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n     *\n     * Emits an {Approval} event.\n     */\n    function approve(address spender, uint256 value) external returns (bool);\n\n    /**\n     * @dev Moves a `value` amount of tokens from `from` to `to` using the\n     * allowance mechanism. `value` is then deducted from the caller's\n     * allowance.\n     *\n     * Returns a boolean value indicating whether the operation succeeded.\n     *\n     * Emits a {Transfer} event.\n     */\n    function transferFrom(address from, address to, uint256 value) external returns (bool);\n}\n"},{"file_path":"contracts/Leadrate.sol","source_code":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {IReserve} from \"./interface/IReserve.sol\";\n\n/**\n * @title Leadrate (attempt at translating the concise German term 'Leitzins')\n *\n * A module that can provide other modules with the leading interest rate for the system.\n *\n **/\ncontract Leadrate {\n    IReserve public immutable equity;\n\n    // The following five variables are less than 256 bits, so they should be stored\n    // in the same slot, making them cheaper to access together, right?\n\n    uint24 public currentRatePPM; // 24 bits allows rates of up to ~1670% per year\n    uint24 public nextRatePPM;\n    uint40 public nextChange;\n\n    uint40 private anchorTime; // 40 bits for time in seconds spans up to 1000 human generations\n    uint64 private ticksAnchor; // in bips * seconds\n\n    event RateProposed(address who, uint24 nextRate, uint40 nextChange);\n    event RateChanged(uint24 newRate);\n\n    error NoPendingChange();\n    error ChangeNotReady();\n\n    constructor(IReserve equity_, uint24 initialRatePPM) {\n        equity = equity_;\n        nextRatePPM = initialRatePPM;\n        currentRatePPM = initialRatePPM;\n        nextChange = uint40(block.timestamp);\n        anchorTime = nextChange;\n        ticksAnchor = 0;\n        emit RateChanged(initialRatePPM); // emit for initialization indexing, if desired\n    }\n\n    /**\n     * Proposes a new interest rate that will automatically be applied after seven days.\n     * To cancel a proposal, just overwrite it with a new one proposing the current rate.\n     */\n    function proposeChange(uint24 newRatePPM_, address[] calldata helpers) external {\n        equity.checkQualified(msg.sender, helpers);\n        nextRatePPM = newRatePPM_;\n        nextChange = uint40(block.timestamp + 7 days);\n        emit RateProposed(msg.sender, nextRatePPM, nextChange);\n    }\n\n    /**\n     * Setting a previously proposed interest rate change into force.\n     */\n    function applyChange() external {\n        if (currentRatePPM == nextRatePPM) revert NoPendingChange();\n        uint40 timeNow = uint40(block.timestamp);\n        if (timeNow < nextChange) revert ChangeNotReady();\n        ticksAnchor += uint64(timeNow - anchorTime) * currentRatePPM;\n        anchorTime = timeNow;\n        currentRatePPM = nextRatePPM;\n        emit RateChanged(currentRatePPM);\n    }\n\n    /**\n     * Total accumulated 'interest ticks' since this contract was deployed.\n     * One 'tick' is a ppm-second, so one month of 12% annual interest is\n     *   120000*30*24*3600 = 311040000000 ticks.\n     * Two months of 6% annual interest would result in the same number of\n     * ticks. For simplicity, this is linear, so there is no \"interest on interest\".\n     */\n    function currentTicks() public view returns (uint64) {\n        return ticks(block.timestamp);\n    }\n\n    function ticks(uint256 timestamp) public view returns (uint64) {\n        return ticksAnchor + (uint64(timestamp) - anchorTime) * currentRatePPM;\n    }\n}\n"},{"file_path":"contracts/interface/IDecentralizedEURO.sol","source_code":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {IReserve} from \"./IReserve.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ninterface IDecentralizedEURO is IERC20 {\n    function suggestMinter(\n        address _minter,\n        uint256 _applicationPeriod,\n        uint256 _applicationFee,\n        string calldata _message\n    ) external;\n\n    function registerPosition(address position) external;\n\n    function denyMinter(address minter, address[] calldata helpers, string calldata message) external;\n\n    function reserve() external view returns (IReserve);\n\n    function minterReserve() external view returns (uint256);\n\n    function calculateAssignedReserve(uint256 mintedAmount, uint32 _reservePPM) external view returns (uint256);\n\n    function calculateFreedAmount(uint256 amountExcludingReserve, uint32 _reservePPM) external view returns (uint256);\n\n    function equity() external view returns (uint256);\n\n    function isMinter(address minter) external view returns (bool);\n\n    function getPositionParent(address position) external view returns (address);\n\n    function mint(address target, uint256 amount) external;\n\n    function mintWithReserve(address target, uint256 amount, uint32 reservePPM) external;\n\n    function burn(uint256 amount) external;\n\n    function burnFrom(address target, uint256 amount) external;\n\n    function burnWithoutReserve(uint256 amount, uint32 reservePPM) external;\n\n    function burnFromWithReserve(\n        address payer,\n        uint256 targetTotalBurnAmount,\n        uint32 reservePPM\n    ) external returns (uint256);\n\n    function coverLoss(address source, uint256 amount) external;\n\n    function distributeProfits(address recipient, uint256 amount) external;\n\n    function collectProfits(address source, uint256 _amount) external;\n}\n"},{"file_path":"contracts/interface/IReserve.sol","source_code":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ninterface IReserve is IERC20 {\n    function invest(uint256 amount, uint256 expected) external returns (uint256);\n    function checkQualified(address sender, address[] calldata helpers) external view;\n    function votesDelegated(address sender, address[] calldata helpers) external view returns (uint256);\n    function totalVotes() external view returns (uint256);\n}\n"}],"certified":false,"conflicting_implementations":null,"abi":[{"inputs":[{"internalType":"contract IDecentralizedEURO","name":"deuro_","type":"address"},{"internalType":"uint24","name":"initialRatePPM","type":"uint24"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ChangeNotReady","type":"error"},{"inputs":[],"name":"ModuleDisabled","type":"error"},{"inputs":[],"name":"NoPendingChange","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint192","name":"amount","type":"uint192"}],"name":"InterestClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"interest","type":"uint256"},{"indexed":false,"internalType":"bool","name":"compounded","type":"bool"}],"name":"InterestCollected","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint24","name":"newRate","type":"uint24"}],"name":"RateChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"who","type":"address"},{"indexed":false,"internalType":"uint24","name":"nextRate","type":"uint24"},{"indexed":false,"internalType":"uint40","name":"nextChange","type":"uint40"}],"name":"RateProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint192","name":"amount","type":"uint192"}],"name":"Saved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint192","name":"amount","type":"uint192"}],"name":"Withdrawn","type":"event"},{"inputs":[{"internalType":"address","name":"accountOwner","type":"address"}],"name":"accruedInterest","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"accountOwner","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"accruedInterest","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint192","name":"targetAmount","type":"uint192"}],"name":"adjust","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"applyChange","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint192","name":"saved","type":"uint192"},{"internalType":"uint64","name":"ticks","type":"uint64"}],"internalType":"struct Savings.Account","name":"account","type":"tuple"},{"internalType":"uint64","name":"ticks","type":"uint64"}],"name":"calculateInterest","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"claimInterest","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"claimableInterest","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentRatePPM","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentTicks","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deuro","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"equity","outputs":[{"internalType":"contract IReserve","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextChange","outputs":[{"internalType":"uint40","name":"","type":"uint40"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextRatePPM","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonCompounding","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint24","name":"newRatePPM_","type":"uint24"},{"internalType":"address[]","name":"helpers","type":"address[]"}],"name":"proposeChange","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"refreshBalance","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"refreshMyBalance","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint192","name":"amount","type":"uint192"},{"internalType":"bool","name":"compound","type":"bool"}],"name":"save","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint192","name":"amount","type":"uint192"}],"name":"save","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint192","name":"amount","type":"uint192"}],"name":"save","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"savings","outputs":[{"internalType":"uint192","name":"saved","type":"uint192"},{"internalType":"uint64","name":"ticks","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"ticks","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint192","name":"amount","type":"uint192"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}],"is_changed_bytecode":false,"is_partially_verified":false,"constructor_args":"0x000000000000000000000000ba3f535bbcccca2a154b573ca6c5a49baae0a3ea0000000000000000000000000000000000000000000000000000000000013880"}