Skip to content
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

[No ticket] Update README, deployment scripts for clarity #199

Merged
merged 4 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,8 @@ MUMBAI_URL=
# Etherscan
ETHERSCAN_API_KEY=
POLYGONSCAN_API_KEY=

# Deployment Configuration
DEPLOY_VERITE_SCHEMA=
DEPLOY_VERITE_VERIFIER=
DEPLOY_TOS_ACCEPTANCE_URL=
72 changes: 64 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,68 @@
# perimeter-core

This repository contains the contracts for the Perimeter protocol.
This repository contains the core Solidity contracts for the Perimeter Protocol, an audited, open-source protocol that anyone can freely use to build unique credit applications using USDC.

## About Perimeter

A project by [Circle Research](https://www.circle.com/en/circle-research), Perimeter offers several important features, such as the ability to delegate loan management and monitoring, integration with [open identity standards](https://www.circle.com/en/verite), and the flexibility to accommodate different types of credit instruments. Perimeter stands out for not relying on a protocol token and its adaptability to various scenarios.

Please read more about Perimeter [in the whitepaper](https://www.circle.com/hubfs/Circle%20Research/Whitepapers/Perimeter_Protocol_Circle_Research.pdf).

## Smart Contract Architecture

Perimeter consists of a number of smart contracts including lending pools, loan constructs, and permissioning helpers.

![](./img/smart_contract_architecture.png)

- Protocol participants include protocol administrators, Pool Admins, Borrowers, and Lenders.

- Global protocol administrative roles (`Admin`, `Pauser`, `Deployer`, `Operator`) are defined through the singleton `ServiceConfiguration` contract.

- PoolAdmins create [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) compliant lending pools through a `PoolFactory`. Pools manage the accounting related to deposits, withdrawals, loan funding, loan payments, and defaults.

- Borrowers create loans through a `LoanFactory`, which are tied to a pool. Each loan is self-contained, with its own lifecycle and maturation process according to its unique schedule and terms. Both open and fixed-term loans are available.

- Lenders deposit into pools to receive pool tokens at an exchange rate determined by the Pool and based on the Pool's current Net Asset Value (NAV). The NAV is computed based on the available liquidity, liquidity outstanding (deployed to loans), and expected interest at that given block.

The Perimeter architecture was designed with modularity in mind, allowing new `PoolFactory` and `LoanFactory` instances to be attached to the protocol through the global `ServiceConfiguration`, potentially creating Pools and Loans with new features.

### Upgradeability

Perimeter allows upgrades to be performed unilaterally by the global `Deployer` role. The following contracts are upgradeable by the `Deployer`:

- "Singleton" contracts like the `ServiceConfiguration`, `PoolAdminAccessControl`, and `ToSAcceptance` can be individually upgraded in-place through the [UUPS](https://docs.openzeppelin.com/contracts/4.x/api/proxy#transparent-vs-uups) pattern.

- The "1-to-N" contracts emitted by factories (`Pool`, `Loan`, `WithdrawController`, `Vault`, `PoolController`) are implemented as Beacon proxies, allowing a single transaction to update an implementation across all proxies simultaneously.

### Permissioning

As noted in the diagram, there are several layers of permissioning, which utilize a combination of [Verite](https://www.circle.com/en/verite)-based, privacy-preserving, decentralized identity credentials and standard allowlists, allowing Pool Admins to determine the appropriate permissioning strategy for their pools.

Specifically, for handling identity credentials, Perimeter follows a verifier pattern, in which trusted verifiers are registered with the protocol and can attest to verifying the privacy-preserving credentials off-chain. This verification result is then recorded on-chain, granting access for a period of time.

Perimeter applies permissioning in several places:

- Permissioning which accounts can create new Pools. This is gated through the `PoolAdminAccessControl`, a singleton contract which maintains a registry of Verite credential attestations for Pool Admins. The specific allowed credential schemas and trusted verifiers can be configured through the protocol `Operator`. Additionally, consenting to the protocol-wide Terms of Service is required, managed through the `ToSAcceptanceRegistry`.

- Permissioning lender access to Pools: a flexible `PoolAccessControl` contract allows individual Pool Admins to specify either a credential-based approach to permissioning or an allow list-based approach (or both) for their given Pool. Prior to enabling access, lenders must first mark their consent to the `ToSAcceptanceRegistry`.

## Getting started

Perimeter is written as a basic [Hardhat](https://hardhat.org/) project. To get started, install the dependencies:

```sh
npm install
```

Copy .env.example and update with appropriate values
Copy .env.example and update with appropriate values:

```sh
cp .env.example .env
```

## Running a node
## Running a localhost node

For local dev, it's helpful to test scripts against a network running locally.

```sh
npx hardhat node
Expand Down Expand Up @@ -63,7 +111,7 @@ npm run test:coverage
npx hardhat coverage
```

### Hardhat Tasks
## Hardhat Tasks

There are several hardhat tasks to allow for easy modification of the contracts.

Expand All @@ -83,10 +131,10 @@ npx hardhat setPaused --help
Here is an example command to pause a hypothetical contract:

```sh
npx hardhat setPaused --address 0x5FbDB2315678afecb367f032d93F642f64180aa3 --paused true
npx hardhat setPaused --address 0x869076ca72531B5474F9182d735a3e3F2e365fc6 --paused true
```

### Getting a Circle Verite Verification Result
## Getting a Verite Verification Result

Update the config in `scripts/verite-verify.ts` with the appropriate values, then run

Expand All @@ -96,9 +144,9 @@ npx hardhat run scripts/verite-verify.ts

This will print out a verification result that can be send to the `verify()` method on the given contract.

### Etherscan Verification
## Etherscan Verification

Contract source can be uploaded and verified to Etherscan. Update `.env` to include your etherscan API key.
Contract source can be uploaded and verified to Etherscan. Update `.env` to include your Etherscan API key.

For each deployed contract, run the following:

Expand All @@ -115,3 +163,11 @@ There are three contracts that require constructor arguments:
```
npx hardhat verify --network goerli CONTRACT_ADDRESS arg1 arg2 arg3
```

## Deployment

There are several deployment scripts available (see `scripts/deploy.ts`). These require a number of values to be set in an `.env` file in the root of the repository.

## Contributing

We welcome contributions, bug fixes, and feature suggestions to Perimeter. Please open an issue or a pull request with your ideas!
Binary file added img/smart_contract_architecture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 21 additions & 3 deletions scripts/deploy-permissionless.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@ type ExtendedHreNetworkConfig = typeof hre.network.config & {
usdcAddress: string | undefined;
};

/**
* @notice A sample deployment script deploying the core contracts of Perimeter, sans permissioning
* @dev 4 key roles are used and configured as part of this deployment process:
* the protocol Admin, the Operator, the Deployer, and the Pauser.
*
* These are read as Ethers signers, and configured from the Hardhat Config using the following environment
* variables set in a .env file:
*
* ACCOUNT_ADMIN
* ACCOUNT_OPERATOR
* ACCOUNT_DEPLOYER
* ACCOUNT_PAUSER
*/
async function main() {
// The token we use for the liquidity asset must exist. If it is not defined, we'll deploy a mock token.
let usdcAddress = (hre.network.config as ExtendedHreNetworkConfig)
Expand Down Expand Up @@ -203,11 +216,16 @@ async function main() {
await tx.wait();
console.log(`ServiceConfiguration: set USDC as a liquidity asset`);

// Set first loss minimum to $10,000
// Set first loss minimum
const firstLossMin = ethers.BigNumber.from(
process.env["DEPLOY_FL_MINIMUM"] ?? 10_000_000000
);
tx = await serviceConfiguration
.connect(operator)
.setFirstLossMinimum(usdcAddress, 10_000_000000);
console.log(`ServiceConfiguration: set USDC first loss minimum to $10,000`);
.setFirstLossMinimum(usdcAddress, firstLossMin);
console.log(
`ServiceConfiguration: set USDC first loss minimum to ${firstLossMin}`
);
}

// We recommend this pattern to be able to use async/await everywhere
Expand Down
77 changes: 54 additions & 23 deletions scripts/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,26 @@ type ExtendedHreNetworkConfig = typeof hre.network.config & {
usdcAddress: string | undefined;
};

/**
* @notice A sample deployment script deploying the core contracts of Perimeter
* @dev 4 key roles are used and configured as part of this deployment process:
* the protocol Admin, the Operator, the Deployer, and the Pauser.
*
* These are read as Ethers signers, and configured from the Hardhat Config using the following environment
* variables set in a .env file:
*
* ACCOUNT_ADMIN
* ACCOUNT_OPERATOR
* ACCOUNT_DEPLOYER
* ACCOUNT_PAUSER
*
* Additionally, several configuration values are used:
*
* DEPLOY_VERITE_SCHEMA - e.g. https://verite.id/definitions/processes/kycaml/0.0.1/generic--usa-legal_person
* DEPLOY_VERITE_VERIFIER - the address of a verifier
* DEPLOY_TOS_ACCEPTANCE_URL - the URL pointing to a ToS requiring consent
* DEPLOY_FL_MINIMUM - The minimum amount of first loss required to be contributed to a pool.
*/
async function main() {
// The token we use for the liquidity asset must exist. If it is not defined, we'll deploy a mock token.
let usdcAddress = (hre.network.config as ExtendedHreNetworkConfig)
Expand Down Expand Up @@ -86,14 +106,16 @@ async function main() {
);

// Set ToSAcceptanceRegsitry URL
const TOS_ACCEPTANCE_REGISTRY_URL = "http://example.com";
const setTosUrlTx = await toSAcceptanceRegistry
.connect(operator)
.updateTermsOfService(TOS_ACCEPTANCE_REGISTRY_URL);
await setTosUrlTx.wait();
console.log(
`ToSAcceptanceRegistry URL set to ${TOS_ACCEPTANCE_REGISTRY_URL}`
);
const TOS_ACCEPTANCE_REGISTRY_URL = process.env["DEPLOY_TOS_ACCEPTANCE_URL"];
if (TOS_ACCEPTANCE_REGISTRY_URL != null) {
const setTosUrlTx = await toSAcceptanceRegistry
.connect(operator)
.updateTermsOfService(TOS_ACCEPTANCE_REGISTRY_URL);
await setTosUrlTx.wait();
console.log(
`ToSAcceptanceRegistry URL set to ${TOS_ACCEPTANCE_REGISTRY_URL}`
);
}

// Update ServiceConfiguration with the ToSAcceptanceRegistry
const setTosRegistryTx = await serviceConfiguration
Expand Down Expand Up @@ -291,26 +313,35 @@ async function main() {
await tx.wait();
console.log(`ServiceConfiguration: set USDC as a liquidity asset`);

// Set first loss minimum to $10,000
// Set first loss minimum
const firstLossMin = ethers.BigNumber.from(
process.env["DEPLOY_FL_MINIMUM"] ?? 10_000_000000
);
tx = await serviceConfiguration
.connect(operator)
.setFirstLossMinimum(usdcAddress, 10_000_000000);
console.log(`ServiceConfiguration: set USDC first loss minimum to $10,000`);
.setFirstLossMinimum(usdcAddress, firstLossMin);
console.log(
`ServiceConfiguration: set USDC first loss minimum to ${firstLossMin}`
);

// Configure Verite
const credentialSchema = [
"https://verite.id/definitions/processes/kycaml/0.0.1/generic--usa-legal_person"
];
const trustedVerifier = "0x76b5A39e3b33317073B0C2a6d1a2Fdaa8300C648"; // Sandbox
const credentialSchema = process.env["DEPLOY_VERITE_SCHEMA"];
const trustedVerifier = process.env["DEPLOY_VERITE_VERIFIER"]; // For demonstration

if (credentialSchema != null) {
tx = await poolAdminAccessControl
.connect(operator)
.addCredentialSchema([credentialSchema]);
console.log(`Added Verite credential schema: ${credentialSchema}`);
}

if (trustedVerifier != null) {
tx = await poolAdminAccessControl
.connect(operator)
.addTrustedVerifier(trustedVerifier);
console.log(`Added Verite trusted verifier: ${trustedVerifier}`);
}

tx = await poolAdminAccessControl
.connect(operator)
.addCredentialSchema(credentialSchema);
console.log(`Added Verite credential schema: ${credentialSchema}`);
tx = await poolAdminAccessControl
.connect(operator)
.addTrustedVerifier(trustedVerifier);
console.log(`Added Verite trusted verifier: ${trustedVerifier}`);
process.exitCode = 0;
process.exit(0);
}
Expand Down
10 changes: 4 additions & 6 deletions scripts/verify-pool-admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@ import { ethers } from "hardhat";
import hre from "hardhat";

async function main() {
// Address of the ToSAcceptanceRegistry contract TODO: this is mumbai
const tosAcceptanceRegistryAddress =
"0x15B0d52d980b58c48c90A479B37e3B93a9bBEd16";
// Address of the PoolAdminAccessControl contract TODO: this is mumbai
const poolAdminAccessControlAddress =
"0x801a90094605123D55A8ea022dB623b6249c2b76";
// Address of the ToSAcceptanceRegistry contract
const tosAcceptanceRegistryAddress = "";
// Address of the PoolAdminAccessControl contract
const poolAdminAccessControlAddress = "";
// The VerificationResult and signature from a Verite verifier. You can run the script
// `verite-verify` to get your own results
const verificationResult = {
Expand Down
5 changes: 2 additions & 3 deletions scripts/verite-verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,13 @@ import jwt from "jsonwebtoken";
// The Chain ID for the network you are using (1 = mainnet, 5 = goerli)
const CHAIN_ID = 5;
// The contract address (PoolAdminAccessControl, etc).
// Default is the Goerli PoolAdminAccessControl address.
const CONTRACT_ADDRESS = "0x3b380e8d02A068ae779b73c7E24c2d18a176BbAD";
const CONTRACT_ADDRESS = "";
// The subject of the VC (the address of the user)
const SUBJECT = "";
// The Verifiable credential JWT for this address
const VC_JWT = "";
// The Circle verifier host
const VERIFIER_HOST = "https://verifier-sandbox.circle.com/api/v1";
const VERIFIER_HOST = "";

/**
* Optional config: You likely do not need to change anything below this line.
Expand Down
Loading