Skip to content

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

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

KevinMomanyi
Copy link

@KevinMomanyi KevinMomanyi commented Mar 22, 2025

#Description of my work
I have implemented the followings in the contract system;

  1. Permissionless deployment of new lending pools through a factory contract
  2. Automatic registration of newly deployed pools with the global ledger
  3. Role-based access control to ensure only registered pools can interact with the global ledger

Closes #613

Copy link
Collaborator

@gitshreevatsa gitshreevatsa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for opening the PR. There are some things to be addressed:

  • Cross shard deployment of Lending pool is not supported in this model. Please go through this for understanding how to deploy to different shards
  • One of the acceptance criteria was to not change existing functionality but the code introduced here, has empty functions.
  • There is a deadlock between constructor of global ledger contract and lending pool factory contract, according to the current implementation introduced I would need global ledger to deploy the factory but to deploy the factory I need the global ledger as well, which is not possible.

Please address these and let us know if you have any issues on implementing the given functionalities.

Also, please mention the issue you are solving, by linking it to this PR.

@0xAleksaOpacic 0xAleksaOpacic added the ODHack12 Tag for issues which are suitable for ODHack week label Mar 26, 2025
@KevinMomanyi
Copy link
Author

hello @gitshreevatsa thanks for your update on some of things I need to address on my issue assigned. I have already made the changes to reflect your guidance and the things we need to address as follows;
Feature requirements;
✅ Factory-Based Pool Deployment – user can deploy a new lending pool with a specified token.
✅ Automatic Global Ledger Registration – Each deployed pool gets registered with the GlobalLedger contract.
✅ Role-Based Access Control – Only registered pools can interact with ledger functions.
✅ Support for Custom Tokens – Each lending pool supports a unique token.

Copy link
Collaborator

@gitshreevatsa gitshreevatsa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please review the changes mentioned

import "@nilfoundation/smart-contracts/contracts/NilTokenBase.sol";

// Axelar Gateway Interface for cross-chain communication
interface IAxelarGateway {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think axelar gateway is something that works over here. As we support our own inter shard communication system. I would prefer you to go through our docs of cross shard communication: here

You would have to deploy a lending pool using the asyncDeploy and then call a registration function.

address gateway,
address token
) external returns (address) {
LendingPool newPool = new LendingPool(globalLedger, lendingPoolChain, gateway, msg.sender, token);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method wouldn't work if the contracts are deployed on different shards

token = _token;
}

function deposit(uint256 amount) external {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that you have changed the deposit function such that it is not backward compatible. One of the main criterias is to make sure backward compatiblity, these changes don't let the interaction remain same.

@KevinMomanyi
Copy link
Author

hello @gitshreevatsa, the changes you suggested have already made in the code such as using the default inter shard communication system, deploy a lending pool using the asyncDeploy and then call a registration function and also backward compatibility. hope this will function, thanks.

Copy link
Collaborator

@gitshreevatsa gitshreevatsa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for some changes, I have left some comments to align on completing this issue.

}

function deployLendingPool() external returns (address) {
LendingPool newPool = new LendingPool(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned previously, this method would just deploy on the same shard but not on other shards. The requirement is to use asyncDeploy and deploy into other shards based on a round robin fashion, have a counter which doesn't exceed 4 and on every deployment it increments so that you have deploy onto different shards.

address poolAddress = address(newPool);

// Asynchronously register the new lending pool with the GlobalLedger
globalLedger.asyncCall(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I doubt this is the signature of asyncCall. You can see the signature here

@@ -5,22 +5,15 @@ import "@nilfoundation/smart-contracts/contracts/Nil.sol";
import "@nilfoundation/smart-contracts/contracts/NilTokenBase.sol";

/// @title LendingPool
/// @dev The LendingPool contract facilitates lending and borrowing of tokens and handles collateral management.
/// It interacts with other contracts such as GlobalLedger, InterestManager, and Oracle for tracking deposits, calculating interest, and fetching token prices.
/// @dev A contract for decentralized lending and borrowing, integrating GlobalLedger, InterestManager, and Oracle.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This contracts still lacks repayment so it wouldn't support backward compatiblity, please add the repayment functions that are associated. They are currently present in the academy.

}

/// @title LendingPool - Individual lending pools for decentralized lending
contract LendingPool {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the extra contract written here when there is already LendingPool.sol in another file. And moreover this contracts lacks a lot of funtionalities. This seems extra contract. You can remove this

return userDeposits[user];
}

function deployLendingPool() external {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused why does the GlobalLedger have deployLendingPool function while it had to be the Factory contract that should have this. Please explain me the arcitecutre that you intend to build, it would be of good discussion to know your thoughts!

@KevinMomanyi
Copy link
Author

Hi @gitshreevatsa I have made some changes to align with the guidance you recommended;
my Architecture Summary includes;
LendingPoolFactory.sol - Handles pool deployment across shards.
LendingPool.sol - Handles deposits, loans, and interactions with GlobalLedger.
GlobalLedger.sol - Only tracks deposits, loans, and repayments.

@idea404 idea404 requested review from ukorvl and gitshreevatsa April 1, 2025 13:21
Copy link
Collaborator

@gitshreevatsa gitshreevatsa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making some changes from previous comments. I think the issue requires these steps:

  • Don't change anything with respect to LendingPool contract(currently in the nil repository, the forked repository has some changes).
  • After asyncDeploy in the LendingPool Factory contract, please add a asyncCall to the GlobalLedger.
  • Change the GlobalLedger to have a role based access function in which only the factory can set the lending pool address and then use that modifier for recordLoan, recordDeposit and recordRepay
  • Also, please create a new Hardhat Task for testing, it would be easier to test.


// @title GlobalLedger
// @dev Tracks deposits, loans, and repayments across LendingPool contracts
contract GlobalLedger {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While there already exists CollateralManager contract, why would we need this GlobalLedger Contract?

/// @param success Indicates if the loan clearing was successful.
/// @param returnData The collateral data returned from the GlobalLedger.
/// @param context The context containing borrower and collateral token.
function releaseCollateral(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without these functions, thee=re would be no colateral release, please dont change them

}

/// @notice Deploys a new LendingPool contract to a shard
function deployLendingPool() external {
Copy link
Collaborator

Choose a reason for hiding this comment

The 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

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well noted Sir, I will make changes

@KevinMomanyi
Copy link
Author

Hello @gitshreevatsa, hope you are well. The cha nges you requested have made some effort to ensure the project fuction smoothly.Below are the changes made;
1. No changes to LendingPool contract
You mentioned that the LendingPool contract should remain untouched as per the canonical version in the nil repo and this has been respected.
2. asyncCall to the GlobalLedger after asyncDeploy in the LendingPoolFactory
The LendingPoolFactory now performs an asyncDeploy for a new lending pool and follows it up with an asyncCall to GlobalLedger.registerLendingPool(address) using the new pool's address.This satisfies the requirement to automatically register the deployed pool with the ledger.
3. Role-based access in GlobalLedger
The GlobalLedger contract:
Inherits from Ownable (factory = owner).
Allows only the factory (owner) to register new lending pools.
Uses a modifier onlyLendingPool to restrict access to recordLoan, recordDeposit, and repayLoan. This meets the requirement of factory-controlled access for authorized lending pools.
4. Hardhat task for deployment/testing
There is now a custom Hardhat task for testing. This makes it much easier to test end-to-end deployment.

Kind regards.

Copy link
Collaborator

@gitshreevatsa gitshreevatsa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for making these changes, there are a few things to address and things are going forward!

shardCounter = (shardCounter + 1) % 4; // Cycle through shards

// Async call to register LendingPool in GlobalLedger
(bool success, ) = globalLedger.call(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The call has to be made via asyncCall because when the global ledger is on different shard compared to the shard where Factory is deployed, this call would fail to work. Just include Nil.asyncCall and no need to resolve it!

@@ -0,0 +1,42 @@
const { task } = require("hardhat/config");

task("deploy-lending-pool", "Deploys a new LendingPool contract and registers it in GlobalLedger")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check the run-lending-protocol task, in that you can see that the contracts are in fact deployed rather than taken from the params.

const factory = LendingPoolFactory.attach(factoryAddress);

// Deploy LendingPool via factory
const tx = await factory.deployLendingPool();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usage of ethers wouldn't work completely with =nil; as we have some differences while calling contracts. Please use niljs. You can learn more about niljs here.


/// @title GlobalLedger
/// @dev Tracks deposits, loans, and repayments across LendingPool contracts
contract GlobalLedger is Ownable {
Copy link
Collaborator

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 GlobalLedger inside CollateralManager.sol why did you want to write an amended contract? I would advise you to use the GlobalLedger inside CollateralManager.sol as it has a lot of functionality that would support backward compatiblity. So only thing you would have to change in GlobalLedger inside CollateralManager.sol would be add factory based role access to it, as your code for factory based role access looks good.

Copy link
Author

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.

Copy link
Collaborator

@gitshreevatsa gitshreevatsa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address the changes mentioned.


// Assuming Nil provides an alternative method for cross-shard calls
// Using Nil.asyncCall to interact with GlobalLedger
(bool success, ) = address(Nil).call(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please change this to use Nil.asyncCall with the correct signature

const { task } = require("hardhat/config");
const { Nil } = require("niljs");

task("deploy-lending-pool", "Deploys a new LendingPool and registers in GlobalLedger")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The contracts have to deployed inside the task and not be taken as arguments of the script.

eth,
} = taskArgs;

const nil = new Nil(hre.network.config.nilProviderUrl); // configure `nilProviderUrl` in hardhat.config.ts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please go through run-lending-pool task file once to see how a client is initiated, how a smart contract/account is deployed and how transactions are sent to the network. This implementation wouldn't work.

@KevinMomanyi
Copy link
Author

hello @gitshreevatsa , some changes made as guided. Thanks

Copy link
Collaborator

@gitshreevatsa gitshreevatsa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address the comments mentioned. Thank you for making previous changes.

FaucetClient,
generateSmartAccount,
waitTillCompleted,
Contract,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no class named Contract that is exposed in niljs


// Cross-shard async call to GlobalLedger to register the new LendingPool
Nil.asyncCall(
0, // Shard ID for GlobalLedger
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A contract cannot be deployed on shard 0. Please change this to the respective shard id of global ledger that you deploy in the script

[deployer, user] = await ethers.getSigners();

// Deploy LendingPoolFactory contract
const LendingPoolFactory = await ethers.getContractFactory("LendingPoolFactory");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You would have to use niljs as I had previously mentioned.

console.log(`Oracle deployed: ${oracle}`);

// Step 3: Deploy LendingPool with linked contracts
const { address: lendingPool, hash: lendingPoolHash } = await deployer.deployContract({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have to deploy the lending pool via the factory not via a separate script

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well noted Sir. Thanks

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good Afternoon @gitshreevatsa,
The changes you guided from my code have tried to address it, am requesting if you can check on it. Thanks.

Copy link
Collaborator

@gitshreevatsa gitshreevatsa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for implementing the changes. Please address these comments

constructorArgs
);

address poolAddress = INilAsync(NIL_ASYNC_PRECOMPILE).asyncDeploy(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can directly use Nil.sol than creating an interface. Please use that

args: [],
});

const { hash: deployHash } = await deployer.execute({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be deloyer.sendTransaction not execute

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @gitshreevatsa ,
I have address the changes requested, thanks.

Copy link
Collaborator

@gitshreevatsa gitshreevatsa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for making changes. Please address the issues with respect to Factory contract and the file structures.

uint8 public shardCounter;

/// @dev Nil precompile interface for async deploy/call
Nil constant nil = Nil(0x0000000000000000000000000000000000008003);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You dont need to do this. Nil.sol directly gives you access to asyncCall or asyncDeploy

0
);

require(success, "asyncCall to GlobalLedger failed");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You dont get a response back from asyncCall precompile

@@ -0,0 +1,113 @@
{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this file present inside comtracts?

0,
0,
bytecode,
0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally the salt argument shouldn,t be set 0. You can instead use a counter

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @gitshreevatsa for your feedback ,
I have address the issues with respect to Factory contract and the file structures.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ODHack12 Tag for issues which are suitable for ODHack week
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[academy] Permissionless Lending Pool Deployment via a Factory in the lending protocol example
4 participants