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

Require pools to have sufficient first loss minimum #106

Merged
merged 1 commit into from
Nov 29, 2022
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
7 changes: 7 additions & 0 deletions contracts/PoolFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ contract PoolFactory is IPoolFactory {
"PoolFactory: Invalid fixed fee interval"
);
}
require(
settings.firstLossInitialMinimum >=
IServiceConfiguration(_serviceConfiguration).firstLossMinimum(
liquidityAsset
),
"PoolFactory: Invalid first loss minimum"
);

// Create the pool
Pool pool = new Pool(
Expand Down
31 changes: 31 additions & 0 deletions contracts/ServiceConfiguration.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ contract ServiceConfiguration is AccessControl, IServiceConfiguration {

mapping(address => bool) public isLiquidityAsset;

mapping(address => uint256) private _firstLossMinimum;

uint256 public firstLossFeeBps = 500;

address public tosAcceptanceRegistry;
Expand All @@ -40,6 +42,11 @@ contract ServiceConfiguration is AccessControl, IServiceConfiguration {
*/
event LiquidityAssetSet(address addr, bool value);

/**
* @dev Emitted when first loss minimum is set for an asset.
*/
event FirstLossMinimumSet(address addr, uint256 value);

/**
* @dev Emitted when a parameter is set.
*/
Expand Down Expand Up @@ -127,6 +134,30 @@ contract ServiceConfiguration is AccessControl, IServiceConfiguration {
emit TermsOfServiceRegistrySet(addr);
}

/**
* @inheritdoc IServiceConfiguration
*/
function setFirstLossMinimum(address addr, uint256 value)
external
override
onlyOperator
{
_firstLossMinimum[addr] = value;
emit FirstLossMinimumSet(addr, value);
}

function firstLossMinimum(address addr)
external
view
override
returns (uint256)
{
return _firstLossMinimum[addr];
}

/**
* @inheritdoc IServiceConfiguration
*/
function setFirstLossFeeBps(uint256 value) external override onlyOperator {
firstLossFeeBps = value;
emit ParameterSet("firstLossFeeBps", value);
Expand Down
9 changes: 9 additions & 0 deletions contracts/interfaces/IServiceConfiguration.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ interface IServiceConfiguration is IAccessControl {

function paused() external view returns (bool);

function firstLossMinimum(address addr) external view returns (uint256);

function firstLossFeeBps() external view returns (uint256);

function isLiquidityAsset(address addr) external view returns (bool);
Expand All @@ -40,6 +42,13 @@ interface IServiceConfiguration is IAccessControl {
*/
function setToSAcceptanceRegistry(address addr) external;

/**
* @dev Sets the first loss minimum for the given asset
* @param addr address of the liquidity asset
* @param value the minimum tokens required to be deposited by pool admins
*/
function setFirstLossMinimum(address addr, uint256 value) external;

/**
* @dev Sets the first loss fee for the protocol
* @param value amount of each payment that is allocated to the first loss vault. Value is in basis points, e.g. 500 equals 5%.
Expand Down
19 changes: 18 additions & 1 deletion test/PoolFactory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ describe("PoolFactory", () => {

return {
poolFactory,
liquidityAsset
liquidityAsset,
serviceConfiguration
};
}

Expand All @@ -66,6 +67,22 @@ describe("PoolFactory", () => {
).to.be.revertedWith("PoolFactory: Invalid duration");
});

it("reverts if the first loss minimum is not sufficient", async () => {
const { serviceConfiguration, poolFactory, liquidityAsset } =
await loadFixture(deployFixture);

// Set a first loss minimum
await serviceConfiguration.setFirstLossMinimum(liquidityAsset.address, 1);

// Attempt to create a pool with 0 first loss minimum
const poolSettings = Object.assign({}, DEFAULT_POOL_SETTINGS, {
firstLossInitialMinimum: 0 // $0
});
await expect(
poolFactory.createPool(liquidityAsset.address, poolSettings)
).to.be.revertedWith("PoolFactory: Invalid first loss minimum");
});

it("emits PoolCreated", async () => {
const { poolFactory, liquidityAsset } = await loadFixture(deployFixture);

Expand Down
37 changes: 37 additions & 0 deletions test/ServiceConfiguration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,43 @@ describe("ServiceConfiguration", () => {
});
});

describe("first loss minimums", () => {
it("default to 0 for any given token", async () => {
const { serviceConfiguration } = await loadFixture(deployFixture);

const { mockERC20 } = await deployMockERC20();

expect(
await serviceConfiguration.firstLossMinimum(
"0x0000000000000000000000000000000000000000"
)
).to.equal(0);

expect(
await serviceConfiguration.firstLossMinimum(mockERC20.address)
).to.equal(0);
});

it("can be updated", async () => {
const { serviceConfiguration } = await loadFixture(deployFixture);

const { mockERC20 } = await deployMockERC20();

expect(
await serviceConfiguration.firstLossMinimum(mockERC20.address)
).to.equal(0);

await serviceConfiguration.setFirstLossMinimum(
mockERC20.address,
10_000_000000 // $10,000
);

expect(
await serviceConfiguration.firstLossMinimum(mockERC20.address)
).to.equal(10_000_000000);
});
});

describe("setFirstLossFeeBps", () => {
it("can only be called by the operator", async () => {
const { serviceConfiguration, operator, otherAccount } =
Expand Down