Skip to content

Commit

Permalink
Merge branch 'main' into fabric-updates
Browse files Browse the repository at this point in the history
  • Loading branch information
onnovisser authored May 3, 2024
2 parents cfc2a48 + 51388c8 commit 95d80d4
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 54 deletions.
2 changes: 1 addition & 1 deletion centrifuge-app/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ const mainnetConfig = {
chainId: 1,
poolRegistryAddress: '0x5ba1e12693dc8f9c48aad8770482f4739beed696',
tinlakeUrl: 'https://tinlake.centrifuge.io',
poolsHash: 'QmRzbEpwFnJE8M4URQEA9JB7pCbh98XnbowXXH8tipoPL3', // TODO: add registry to config and fetch poolHash
poolsHash: 'QmaMA1VYSKuuYhBcQCyf5Ek4VoiiEG6oLGp3iGbsQPGpkS', // TODO: add registry to config and fetch poolHash
blockExplorerUrl: 'https://etherscan.io',
}

Expand Down
39 changes: 23 additions & 16 deletions centrifuge-app/src/pages/IssuerPool/Access/PoolManagers.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ComputedMultisig, computeMultisig, PoolMetadata } from '@centrifuge/centrifuge-js'
import { useCentrifugeTransaction } from '@centrifuge/centrifuge-react'
import { Button } from '@centrifuge/fabric'
import { Button, Text } from '@centrifuge/fabric'
import { Form, FormikProvider, useFormik } from 'formik'
import * as React from 'react'
import { combineLatest, switchMap } from 'rxjs'
Expand All @@ -12,6 +12,7 @@ import { diffPermissions } from '../Configuration/Admins'
import { MultisigForm } from './MultisigForm'

export type PoolManagersInput = {
enabled: boolean
adminMultisig: {
signers: string[]
threshold: number
Expand All @@ -28,6 +29,7 @@ export function PoolManagers({ poolId }: { poolId: string }) {

const initialValues: PoolManagersInput = React.useMemo(
() => ({
enabled: !!access.multisig,
adminMultisig: {
signers: access.multisig?.signers || [],
threshold: access.multisig?.threshold || 1,
Expand All @@ -36,15 +38,6 @@ export function PoolManagers({ poolId }: { poolId: string }) {
[access?.multisig]
)

const storedManagerPermissions = poolPermissions
? Object.entries(poolPermissions)
.filter(([addr, p]) => p.roles.length && initialValues.adminMultisig.signers.includes(addr))
.map(([address, permissions]) => ({
address,
roles: Object.fromEntries(permissions.roles.map((role) => [role, true])),
}))
: []

const { execute, isLoading } = useCentrifugeTransaction(
'Update pool managers',
(cent) =>
Expand All @@ -71,7 +64,7 @@ export function PoolManagers({ poolId }: { poolId: string }) {
metadataTx,
...permissionTx.method.args[0],
api.tx.proxy.addProxy(newMultisig.address, 'Any', 0),
api.tx.proxy.removeProxy(access.multisig!.address, 'Any', 0),
...access.adminDelegates.map((proxy) => api.tx.proxy.removeProxy(proxy.delegatee, 'Any', 0)),
])
return cent.wrapSignAndSend(api, tx, options)
})
Expand Down Expand Up @@ -103,7 +96,7 @@ export function PoolManagers({ poolId }: { poolId: string }) {
[
newMultisig,
diffPermissions(
storedManagerPermissions,
access.managerPermissions,
values.adminMultisig.signers.map((address) => ({
address,
roles: { InvestorAdmin: true, LiquidityAdmin: true },
Expand All @@ -130,15 +123,25 @@ export function PoolManagers({ poolId }: { poolId: string }) {
adminMultisig.signers.length !== initialValues.adminMultisig.signers.length ||
!adminMultisig.signers.every((s) => initialValues.adminMultisig.signers.includes(s))

if (!access.multisig) return null

return (
<FormikProvider value={form}>
<Form>
<PageSection
title="Pool managers"
headerRight={
isEditing ? (
!form.values.enabled ? (
<Button
variant="secondary"
onClick={() => {
setIsEditing(true)
form.setFieldValue('enabled', true, false)
}}
small
key="edit"
>
Enable
</Button>
) : isEditing ? (
<ButtonGroup variant="small">
<Button variant="secondary" onClick={() => setIsEditing(false)} small>
Cancel
Expand All @@ -161,7 +164,11 @@ export function PoolManagers({ poolId }: { poolId: string }) {
)
}
>
<MultisigForm isEditing={isEditing} isLoading={isLoading} />
{!form.values.enabled ? (
<Text>Pool managers not enabled</Text>
) : (
<MultisigForm isEditing={isEditing} isLoading={isLoading} />
)}
</PageSection>
</Form>
</FormikProvider>
Expand Down
27 changes: 19 additions & 8 deletions centrifuge-app/src/pages/Loan/RepayForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ActiveLoan, CurrencyBalance, findBalance } from '@centrifuge/centrifuge-js'
import { ActiveLoan, CurrencyBalance, Rate, findBalance } from '@centrifuge/centrifuge-js'
import { useBalances, useCentrifugeTransaction } from '@centrifuge/centrifuge-react'
import { Button, Card, CurrencyInput, InlineFeedback, Shelf, Stack, Text } from '@centrifuge/fabric'
import BN from 'bn.js'
Expand Down Expand Up @@ -63,14 +63,25 @@ function InternalRepayForm({ loan }: { loan: ActiveLoan }) {
amount: '',
},
onSubmit: (values, actions) => {
const outstandingPrincipal = loan.totalBorrowed.sub(loan.repaid.principal)
let amount: BN = CurrencyBalance.fromFloat(values.amount, pool.currency.decimals)
let interest = new BN(0)
if (amount.gt(outstandingPrincipal)) {
interest = amount.sub(outstandingPrincipal)
amount = outstandingPrincipal
// Pay the interest with a small margin first, then the principal
let interest: BN = CurrencyBalance.fromFloat(values.amount, pool.currency.decimals)
let principal = new BN(0)

// Calculate interest from the time the loan was fetched until now
const time = Date.now() - loan.fetchedAt.getTime()
const margin = CurrencyBalance.fromFloat(
loan.outstandingPrincipal
.toDecimal()
.mul(Rate.fractionFromApr(loan.pricing.interestRate.toDecimal()).toDecimal())
.mul(time),
pool.currency.decimals
)
const interestWithMargin = loan.outstandingInterest.add(margin)
if (interest.gt(interestWithMargin)) {
principal = interest.sub(interestWithMargin)
interest = interestWithMargin
}
doRepayTransaction([loan.poolId, loan.id, amount, interest, new BN(0)], { account, forceProxyType: 'Borrow' })
doRepayTransaction([loan.poolId, loan.id, principal, interest, new BN(0)], { account, forceProxyType: 'Borrow' })
actions.setSubmitting(false)
},
validateOnMount: true,
Expand Down
41 changes: 24 additions & 17 deletions centrifuge-app/src/pages/Loan/TransferDebtForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ export function TransferDebtForm({ loan }: { loan: LoanType }) {
let repay: any = { principal, interest }
if (isExternalLoan(selectedLoan)) {
const repayPriceBN = CurrencyBalance.fromFloat(form.values.targetLoanPrice || 1, pool.currency.decimals)
const repayQuantityBN = Price.fromFloat(Dec(values.targetLoanFaceValue || 0).div(selectedLoan.pricing.notional.toDecimal()))
const repayQuantityBN = Price.fromFloat(
Dec(values.targetLoanFaceValue || 0).div(selectedLoan.pricing.notional.toDecimal())
)
repay = { quantity: repayQuantityBN, price: repayPriceBN, interest }
}

Expand All @@ -97,26 +99,28 @@ export function TransferDebtForm({ loan }: { loan: LoanType }) {
})
actions.setSubmitting(false)
},
validate(values) {
validate(values) {
const financeAmount = isExternalLoan(loan)
? Dec(values.price || 0)
.mul(Dec(values.faceValue || 0))
.div(loan.pricing.notional.toDecimal())
: selectedLoan && isExternalLoan(selectedLoan) ? Dec(values.targetLoanPrice || 0)
.mul(Dec(values.targetLoanFaceValue || 0))
.div(selectedLoan.pricing.notional.toDecimal()): Dec(values.amount || 0)
: selectedLoan && isExternalLoan(selectedLoan)
? Dec(values.targetLoanPrice || 0)
.mul(Dec(values.targetLoanFaceValue || 0))
.div(selectedLoan.pricing.notional.toDecimal())
: Dec(values.amount || 0)

let errors: any = {}
const error = validate(financeAmount)

const error = validate(financeAmount)
if (error) {
if (selectedLoan && isExternalLoan(selectedLoan)) {
errors = setIn(errors, 'targetLoanPrice', error)
} else {
errors = setIn(errors, 'amount', error)
}
}

return errors
},
})
Expand Down Expand Up @@ -151,9 +155,11 @@ export function TransferDebtForm({ loan }: { loan: LoanType }) {
? Dec(form.values.price || 0)
.mul(Dec(form.values.faceValue || 0))
.div(loan.pricing.notional.toDecimal())
: selectedLoan && isExternalLoan(selectedLoan) ? Dec(form.values.targetLoanPrice || 0)
.mul(Dec(form.values.targetLoanFaceValue || 0))
.div(selectedLoan.pricing.notional.toDecimal()): Dec(form.values.amount || 0)
: selectedLoan && isExternalLoan(selectedLoan)
? Dec(form.values.targetLoanPrice || 0)
.mul(Dec(form.values.targetLoanFaceValue || 0))
.div(selectedLoan.pricing.notional.toDecimal())
: Dec(form.values.amount || 0)

return (
<Stack as={Card} gap={2} p={2}>
Expand Down Expand Up @@ -239,7 +245,10 @@ export function TransferDebtForm({ loan }: { loan: LoanType }) {
)
}}
</Field>
<Field name="targetLoanPrice" validate={combine(settlementPrice(), maxPriceVariance(selectedLoan.pricing))}>
<Field
name="targetLoanPrice"
validate={combine(settlementPrice(), maxPriceVariance(selectedLoan.pricing))}
>
{({ field, meta, form }: FieldProps) => {
return (
<CurrencyInput
Expand All @@ -255,9 +264,7 @@ export function TransferDebtForm({ loan }: { loan: LoanType }) {
</Field>
<Shelf justifyContent="space-between">
<Text variant="emphasized">Total amount</Text>
<Text variant="emphasized">
{formatBalance(financeAmount, pool?.currency.symbol, 2)}
</Text>
<Text variant="emphasized">{formatBalance(financeAmount, pool?.currency.symbol, 2)}</Text>
</Shelf>
</>
)}
Expand All @@ -276,8 +283,8 @@ function LoanOption({ loan }: { loan: Loan }) {
const nft = useCentNFT(loan.asset.collectionId, loan.asset.nftId, false, false)
const { data: metadata } = useMetadata(nft?.metadataUri, nftMetadataSchema)
return (
<option value={loan.id}>
<>
{loan.id} - {metadata?.name}
</option>
</>
)
}
4 changes: 2 additions & 2 deletions centrifuge-app/src/utils/useLoans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ export function useAvailableFinancing(poolId: string, assetId: string) {
if (loan.status !== 'Active') return { current: initialCeiling, initial: initialCeiling }

const debtWithMargin =
'interestRate' in loan.pricing
'interestRate' in loan.pricing && 'outstandingPrincipal' in loan
? loan.outstandingDebt
.toDecimal()
.add(loan.outstandingDebt.toDecimal().mul(loan.pricing.interestRate.toDecimal().div(365 * 8))) // Additional 3 hour interest as margin
.add(loan.outstandingPrincipal.toDecimal().mul(loan.pricing.interestRate.toDecimal().div(365 * 8))) // Additional 3 hour interest as margin
: Dec(0)

let ceiling = initialCeiling
Expand Down
3 changes: 3 additions & 0 deletions centrifuge-app/src/utils/usePermissions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ export function usePoolAccess(poolId: string) {
roles: Object.fromEntries(permissions.roles.map((role) => [role, true])),
}))
: []

const missingAdminPermissions = diffPermissions(
[storedAdminRoles],
[{ address: storedAdminRoles.address, roles: { InvestorAdmin: true } }]
Expand All @@ -361,6 +362,8 @@ export function usePoolAccess(poolId: string) {
[metadata?.adminMultisig]
),
adminPermissions,
adminDelegates,
managerPermissions: storedManagerPermissions,
missingPermissions: [...missingAdminPermissions, ...missingManagerPermissions],
missingAdminPermissions,
missingManagerPermissions,
Expand Down
2 changes: 1 addition & 1 deletion centrifuge-app/tests/e2e/specs/invest.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ describe('Invest flows', () => {
cy.get('input[name="amount"]').type(pool.investAmount)
cy.get('button[type="submit"]').click()
cy.confirmTransaction()
cy.contains(`Invested ${pool.poolCurrency} value ${pool.investAmount} ${pool.poolCurrency}`).should('exist')
cy.contains(`Invested ${pool.investAmount} ${pool.poolCurrency}`).should('exist')
})
it('Pool Admin: Close epoch (execute investment)', () => {
cy.visit('/pools', { failOnStatusCode: false })
Expand Down
6 changes: 1 addition & 5 deletions centrifuge-js/src/modules/liquidityPools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,8 @@ export function getLiquidityPoolsModule(inst: Centrifuge) {
api.tx.liquidityPools.addPool(poolId, { EVM: chainId }),
...pool.tranches.ids.flatMap((trancheId: string) => [
api.tx.liquidityPools.addTranche(poolId, trancheId, { EVM: chainId }),
// Ensure the domain currencies are enabled
// Using a batch, because theoretically they could have been enabled already for a different domain
api.tx.utility.batch(
currencies.map((cur) => api.tx.liquidityPools.allowInvestmentCurrency(poolId, cur.key))
),
]),
...currencies.map((cur) => api.tx.liquidityPools.allowInvestmentCurrency(poolId, cur.key)),
])
return inst.wrapSignAndSend(api, tx, options)
})
Expand Down
12 changes: 9 additions & 3 deletions centrifuge-js/src/modules/pools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,7 @@ export type TinlakeLoan = {
// transformed type for UI
export type CreatedLoan = {
status: 'Created'
fetchedAt: Date
id: string
poolId: string
pricing: PricingInfo
Expand All @@ -481,6 +482,7 @@ export type CreatedLoan = {
// transformed type for UI
export type ActiveLoan = {
status: 'Active'
fetchedAt: Date
id: string
poolId: string
pricing: PricingInfo
Expand Down Expand Up @@ -511,6 +513,7 @@ export type ActiveLoan = {
// transformed type for UI
export type ClosedLoan = {
status: 'Closed'
fetchedAt: Date
id: string
poolId: string
pricing: PricingInfo
Expand Down Expand Up @@ -3074,7 +3077,7 @@ export function getPoolsModule(inst: Centrifuge) {
filter(({ api, events }) => {
const event = events.find(
({ event }) =>
api.events.priceOracle.NewFeedData.is(event) ||
api.events.oraclePriceFeed.Fed.is(event) ||
api.events.loans.Created.is(event) ||
api.events.loans.Borrowed.is(event) ||
api.events.loans.Repaid.is(event) ||
Expand All @@ -3083,10 +3086,9 @@ export function getPoolsModule(inst: Centrifuge) {
api.events.loans.Closed.is(event) ||
api.events.loans.PortfolioValuationUpdated.is(event)
)

if (!event) return false

const { poolId: eventPoolId } = (event.toHuman() as any).event.data
if (!eventPoolId) return true
return eventPoolId.replace(/\D/g, '') === poolId
})
)
Expand Down Expand Up @@ -3181,6 +3183,10 @@ export function getPoolsModule(inst: Centrifuge) {
? pricingInfo.valuationMethod.discountedCashFlow
: undefined
return {
// Return the time the loans were fetched, in order to calculate a more accurate/up-to-date outstandingInterest
// Mainly for when repaying interest, to repay as close to the correct amount of interest
// Refetching before repaying would be another ideas, but less practical with substriptions
fetchedAt: new Date(),
asset: {
collectionId: collectionId.toString(),
nftId: nftId.toString(),
Expand Down
2 changes: 1 addition & 1 deletion onboarding-api/src/utils/networks/tinlake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ const goerliConfig = {
memberListAddress: '0xaEcFA11fE9601c1B960661d7083A08A5df7c1947',
}
const mainnetConfig = {
poolsHash: 'QmRzbEpwFnJE8M4URQEA9JB7pCbh98XnbowXXH8tipoPL3', // TODO: add registry to config and fetch poolHash
poolsHash: 'QmaMA1VYSKuuYhBcQCyf5Ek4VoiiEG6oLGp3iGbsQPGpkS', // TODO: add registry to config and fetch poolHash
memberListAddress: '0xB7e70B77f6386Ffa5F55DDCb53D87A0Fb5a2f53b',
}

Expand Down

0 comments on commit 95d80d4

Please sign in to comment.