-
Notifications
You must be signed in to change notification settings - Fork 41
add permissionless lending features #634
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 9 commits
d9aa3f4
d27980a
f2e698e
95c6283
26a6306
d6900f6
323c89e
09f87d3
b7bf558
995a7c0
4c847d2
52a13ab
996a70f
e1973e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| // SPDX-License-Identifier: UNLICENSED | ||
| pragma solidity ^0.8.28; | ||
|
|
||
| import "@openzeppelin/contracts/access/Ownable.sol"; | ||
|
|
||
| /// @title GlobalLedger | ||
| /// @dev Tracks deposits, loans, and repayments across LendingPool contracts | ||
| contract GlobalLedger is Ownable { | ||
| struct Deposit { | ||
| uint256 amount; | ||
| bool exists; | ||
| } | ||
|
|
||
| struct Loan { | ||
| uint256 amount; | ||
| bool exists; | ||
| } | ||
|
|
||
| mapping(address => mapping(TokenId => Deposit)) public deposits; | ||
| mapping(address => mapping(TokenId => Loan)) public loans; | ||
| mapping(address => bool) public authorizedLendingPools; | ||
|
|
||
| event DepositRecorded(address indexed user, TokenId token, uint256 amount); | ||
| event LoanRecorded(address indexed user, TokenId token, uint256 amount); | ||
| event LoanRepaid(address indexed user, TokenId token, uint256 amount); | ||
| event LendingPoolRegistered(address indexed lendingPool); | ||
|
|
||
| /// @notice Modifier to restrict access to only authorized lending pools | ||
| modifier onlyLendingPool() { | ||
| require( | ||
| authorizedLendingPools[msg.sender], | ||
| "Not an authorized lending pool" | ||
| ); | ||
| _; | ||
| } | ||
|
|
||
| /// @notice Register a new lending pool (only callable by factory or internal async call) | ||
| function registerLendingPool(address lendingPool) external { | ||
| require( | ||
| msg.sender == owner() || msg.sender == address(this), | ||
| "Unauthorized" | ||
| ); | ||
| authorizedLendingPools[lendingPool] = true; | ||
| emit LendingPoolRegistered(lendingPool); | ||
| } | ||
|
|
||
| /// @notice Record a deposit from an authorized LendingPool | ||
| function recordDeposit( | ||
| address user, | ||
| TokenId token, | ||
| uint256 amount | ||
| ) external payable onlyLendingPool { | ||
| require(amount > 0, "Invalid deposit amount"); | ||
|
|
||
| if (!deposits[user][token].exists) { | ||
| deposits[user][token] = Deposit(amount, true); | ||
| } else { | ||
| deposits[user][token].amount += amount; | ||
| } | ||
|
|
||
| emit DepositRecorded(user, token, amount); | ||
| } | ||
|
|
||
| /// @notice Record a loan taken from an authorized LendingPool | ||
| function recordLoan( | ||
| address user, | ||
| TokenId token, | ||
| uint256 amount | ||
| ) external payable onlyLendingPool { | ||
| require(amount > 0, "Invalid loan amount"); | ||
|
|
||
| if (!loans[user][token].exists) { | ||
| loans[user][token] = Loan(amount, true); | ||
| } else { | ||
| loans[user][token].amount += amount; | ||
| } | ||
|
|
||
| emit LoanRecorded(user, token, amount); | ||
| } | ||
|
|
||
| /// @notice Record loan repayment and reduce outstanding balance | ||
| function repayLoan( | ||
| address user, | ||
| TokenId token, | ||
| uint256 amount | ||
| ) external payable onlyLendingPool { | ||
| require(amount > 0, "Invalid repayment amount"); | ||
| require(loans[user][token].exists, "No active loan"); | ||
|
|
||
| if (loans[user][token].amount <= amount) { | ||
| delete loans[user][token]; | ||
| } else { | ||
| loans[user][token].amount -= amount; | ||
| } | ||
|
|
||
| emit LoanRepaid(user, token, amount); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| // SPDX-License-Identifier: UNLICENSED | ||
| pragma solidity ^0.8.28; | ||
|
|
||
| import "@nilfoundation/smart-contracts/contracts/Nil.sol"; | ||
| import "./LendingPool.sol"; // Import LendingPool contract | ||
|
|
||
| /// @title LendingPoolFactory | ||
| /// @dev Handles deployment of LendingPool contracts across different shards | ||
| contract LendingPoolFactory { | ||
| address public globalLedger; | ||
| address public interestManager; | ||
| address public oracle; | ||
| TokenId public usdt; | ||
| TokenId public eth; | ||
| uint8 public shardCounter; // Tracks which shard to deploy to (0-3) | ||
|
|
||
| event LendingPoolDeployed(address pool, uint8 shardId, address owner); | ||
|
|
||
| constructor( | ||
| address _globalLedger, | ||
| address _interestManager, | ||
| address _oracle, | ||
| TokenId _usdt, | ||
| TokenId _eth | ||
| ) { | ||
| globalLedger = _globalLedger; | ||
| interestManager = _interestManager; | ||
| oracle = _oracle; | ||
| usdt = _usdt; | ||
| eth = _eth; | ||
| shardCounter = 0; | ||
| } | ||
|
|
||
| /// @notice Deploys a new LendingPool contract to a shard | ||
| function deployLendingPool() external { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the deployment of the contract, there should be a function which calls the globalLedger to register the lending pool
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. well noted Sir, I will make changes |
||
| bytes memory constructorArgs = abi.encode( | ||
| globalLedger, | ||
| interestManager, | ||
| oracle, | ||
| usdt, | ||
| eth, | ||
| msg.sender | ||
| ); | ||
|
|
||
| // Compute full contract creation bytecode | ||
| bytes memory bytecode = bytes.concat( | ||
| type(LendingPool).creationCode, | ||
| constructorArgs | ||
| ); | ||
|
|
||
| // Call asyncDeploy with correct parameters | ||
| address poolAddress = Nil.asyncDeploy( | ||
| shardCounter, // Shard ID | ||
| msg.sender, // Refund to the sender | ||
| address(0), // Bounce to (set to 0 for now) | ||
| 0, // Fee credit (set to 0 unless required) | ||
| 0, // Forward kind (set to 0 unless forwarding behavior is needed) | ||
| 0, // Value (set to 0 unless ETH needs to be sent) | ||
| bytecode, // Contract creation code + constructor args | ||
| 0 // Salt (set to 0; can be changed for deterministic addresses) | ||
| ); | ||
|
|
||
| require(poolAddress != address(0), "Deployment failed"); | ||
|
|
||
| emit LendingPoolDeployed(poolAddress, shardCounter, msg.sender); | ||
| shardCounter = (shardCounter + 1) % 4; // Cycle through shards | ||
|
|
||
| // Async call to register LendingPool in GlobalLedger | ||
| (bool success, ) = globalLedger.call( | ||
|
||
| abi.encodeWithSignature("registerLendingPool(address)", poolAddress) | ||
| ); | ||
| require(success, "Async call to GlobalLedger failed"); | ||
| } | ||
| } | ||
ukorvl marked this conversation as resolved.
Show resolved
Hide resolved
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| const { task } = require("hardhat/config"); | ||
|
|
||
| task("deploy-lending-pool", "Deploys a new LendingPool contract and registers it in GlobalLedger") | ||
|
||
| .addParam("factory", "The address of the LendingPoolFactory contract") | ||
| .addParam("globalLedger", "The address of the GlobalLedger contract") | ||
| .addParam("interestManager", "The address of the InterestManager contract") | ||
| .addParam("oracle", "The address of the Oracle contract") | ||
| .addParam("usdt", "The TokenId for USDT") | ||
| .addParam("eth", "The TokenId for ETH") | ||
| .setAction(async (taskArgs) => { | ||
| const { ethers } = require("hardhat"); | ||
|
|
||
| const factoryAddress = taskArgs.factory; | ||
| const globalLedgerAddress = taskArgs.globalLedger; | ||
| const interestManagerAddress = taskArgs.interestManager; | ||
| const oracleAddress = taskArgs.oracle; | ||
| const usdtTokenId = taskArgs.usdt; | ||
| const ethTokenId = taskArgs.eth; | ||
|
|
||
| console.log("Deploying LendingPool using the provided factory..."); | ||
|
|
||
| // Attach to LendingPoolFactory contract | ||
| const LendingPoolFactory = await ethers.getContractFactory("LendingPoolFactory"); | ||
| const factory = LendingPoolFactory.attach(factoryAddress); | ||
|
|
||
| // Deploy LendingPool via factory | ||
| const tx = await factory.deployLendingPool(); | ||
|
||
| await tx.wait(); // Wait for the transaction to be mined | ||
| console.log("LendingPool deployed successfully!"); | ||
|
|
||
| // Interact with GlobalLedger to check if the LendingPool is registered | ||
| const GlobalLedger = await ethers.getContractFactory("GlobalLedger"); | ||
| const globalLedger = GlobalLedger.attach(globalLedgerAddress); | ||
|
|
||
| // Check if LendingPool is registered in GlobalLedger | ||
| const isRegistered = await globalLedger.authorizedLendingPools(factoryAddress); | ||
| console.log("Is LendingPool registered in GlobalLedger?", isRegistered); | ||
|
|
||
| // You can also test other functionality like registering a deposit, loan, etc. | ||
| }); | ||
|
|
||
| module.exports = {}; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While we already have the
GlobalLedgerinsideCollateralManager.solwhy did you want to write an amended contract? I would advise you to use theGlobalLedgerinsideCollateralManager.solas it has a lot of functionality that would support backward compatiblity. So only thing you would have to change inGlobalLedgerinsideCollateralManager.solwould be add factory based role access to it, as your code for factory based role access looks good.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello @gitshreevatsa, thanks for the update. I have made some changes on the code incuding removing Globalledger.sol to avoid duplicate and others.