Skip to content
This repository has been archived by the owner on Feb 18, 2021. It is now read-only.

Commit

Permalink
Merge pull request #190 from centrifuge/linear-solver
Browse files Browse the repository at this point in the history
Linear solver setup
  • Loading branch information
Jeroen Offerijns authored Sep 2, 2020
2 parents ce1eab1 + 72338c1 commit c4a6a54
Show file tree
Hide file tree
Showing 7 changed files with 322 additions and 5 deletions.
8 changes: 7 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@types/node": "^13.11.0",
"@types/web3-eth-abi": "^1.0.0",
"declaration-bundler-webpack-plugin": "^1.0.3",
"glpk.js": "3.1.2",
"husky": "^4.2.3",
"nodemon": "^1.19.1",
"prettier": "2.1.1",
Expand All @@ -47,7 +48,7 @@
},
"scripts": {
"build": "rollup -c",
"watch": "rollup -cw",
"start": "rollup -cw",
"test": "ts-mocha -p src/test/tsconfig.json src/**/*.spec.ts src/*.spec.ts --timeout 40000",
"nodemon": "nodemon node inspect dist/Tinlake.js",
"generate-docs": "typedoc --out docs --exclude \"./node_modules/**\" --exclude \"./src/abi/**\" --exclude \"./src/index.ts\" --exclude \"./src/actions/*.spec.ts\" --exclude \"./src/test/**\" --excludeExternals --excludeNotExported --ignoreCompilerErrors ./src",
Expand Down
125 changes: 125 additions & 0 deletions src/actions/coordinator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import assert from 'assert'
import { ITinlake } from '../types/tinlake'
import { createTinlake } from '../test/utils'
import testConfig from '../test/config'

let tinlake: ITinlake

describe('coordinator tests', async () => {
before(async () => {
tinlake = createTinlake(testConfig.godAccount, testConfig)
})

describe('epoch solver', async () => {
it('should return an optimal solution when limited by the max TIN ratio', async () => {
const state = {
netAssetValue: 800,
reserve: 200,
seniorDebt: 700,
seniorBalance: 100,
minTinRatio: 0.15,
maxTinRatio: 0.2,
maxReserve: 10000,
}

const orderState = {
tinRedeemOrder: 100,
dropRedeemOrder: 300,
tinInvestOrder: 200,
dropInvestOrder: 400,
}

const result = await tinlake.calculateOptimalSolution(state, orderState)

assert.equal(result.status, 5)
assert.equal(result.z > 0, true)
assert.equal(result.vars.tinRedeem, 100)
assert.equal(result.vars.dropRedeem, 300)
assert.equal(result.vars.tinInvest, 125)
assert.equal(result.vars.dropInvest, 400)
})

it('should return an optimal solution when limited by the max reserve', async () => {
// The gap between the maxReserve and reserve is 300, so 300 tokens can be invested.
const state = {
netAssetValue: 800,
reserve: 200,
seniorDebt: 700,
seniorBalance: 100,
minTinRatio: 0.0,
maxTinRatio: 1.0,
maxReserve: 500,
}

// 50 is redeemed, so 300+50=350 can be invested.
const orderState = {
tinRedeemOrder: 0,
dropRedeemOrder: 50,
tinInvestOrder: 200,
dropInvestOrder: 200,
}

const result = await tinlake.calculateOptimalSolution(state, orderState)

// The full redeem is possible, while only 350/400 of total invest orders are possible.
// TIN investments have preference over DROP investments, so the full TIN invest order is fulfilled, while the tin invest order is limited by 150.
assert.equal(result.status, 5)
assert.equal(result.z > 0, true)
assert.equal(result.vars.tinRedeem, 0)
assert.equal(result.vars.dropRedeem, 50)
assert.equal(result.vars.tinInvest, 200)
assert.equal(result.vars.dropInvest, 150)
})

it('should return no feasible solution if the input state is unhealthy', async () => {
const state = {
netAssetValue: 800,
reserve: 200,
seniorDebt: 700,
seniorBalance: 100,
minTinRatio: 0.01,
maxTinRatio: 0.01,
maxReserve: 500,
}

const orderState = {
tinRedeemOrder: 100,
dropRedeemOrder: 200,
tinInvestOrder: 300,
dropInvestOrder: 400,
}

const result = await tinlake.calculateOptimalSolution(state, orderState)
assert.equal(result.status, 4)
assert.equal(result.z, 0)
})

it('should return a feasible solution if the input state is unhealthy, but a feasible solution can be found using the orders', async () => {
const state = {
netAssetValue: 800,
reserve: 200,
seniorDebt: 700,
seniorBalance: 100,
minTinRatio: 0.01,
maxTinRatio: 0.01,
maxReserve: 50000,
}

const orderState = {
tinRedeemOrder: 10000,
dropRedeemOrder: 10000,
tinInvestOrder: 10000,
dropInvestOrder: 10000,
}

const result = await tinlake.calculateOptimalSolution(state, orderState)

assert.equal(result.status, 5)
assert.equal(result.z > 0, true)
assert.equal(result.vars.tinRedeem > 0, true)
assert.equal(result.vars.dropRedeem > 0, true)
assert.equal(result.vars.tinInvest > 0, true)
assert.equal(result.vars.dropInvest > 0, true)
})
})
})
180 changes: 180 additions & 0 deletions src/actions/coordinator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import { Constructor, TinlakeParams } from '../Tinlake'

export function CoordinatorActions<ActionsBase extends Constructor<TinlakeParams>>(Base: ActionsBase) {
return class extends Base implements ICoordinatorActions {
solveEpoch = async () => {
// const tinlake = (this as any)
// const reserve = (await tinlake.getJuniorReserve()).add(await tinlake.getSeniorReserve())

// const state = {
// reserve,
// netAssetValue: 0,
// seniorDebt: await tinlake.getSeniorDebt(),
// seniorBalance: 0,
// minTinRatio: await tinlake.getMinJuniorRatio(),
// maxTinRatio: 0,
// maxReserve: 0,
// }

return Promise.resolve({
tinRedeem: 1,
dropRedeem: 2,
tinInvest: 3,
dropInvest: 4
})
}

calculateOptimalSolution = async (state: State, orderState: OrderState) => {
return require('glpk.js').then((glpk: any) => {
const lp = {
name: 'LP',
generals: ['dropRedeem', 'tinRedeem', 'tinInvest', 'dropInvest'],
objective: {
// Maximize: dropRedeem > tinRedeem > tinInvest > dropInvest
direction: glpk.GLP_MAX,
name: 'obj',
vars: [
{ name: 'dropRedeem', coef: 10000 },
{ name: 'tinRedeem', coef: 1000 },
{ name: 'tinInvest', coef: 100 },
{ name: 'dropInvest', coef: 10 },
],
},
subjectTo: [
{
name: 'currencyAvailable',
vars: [
{ name: 'tinInvest', coef: 1.0 },
{ name: 'dropInvest', coef: 1.0 },
{ name: 'tinRedeem', coef: -1.0 },
{ name: 'dropRedeem', coef: -1.0 },
],
bnds: { type: glpk.GLP_LO, ub: 0.0, lb: -state.reserve },
},
{
name: 'dropRedeemOrder',
vars: [{ name: 'dropRedeem', coef: 1.0 }],
bnds: { type: glpk.GLP_UP, ub: orderState.dropRedeemOrder, lb: 0.0 },
},
{
name: 'tinRedeemOrder',
vars: [{ name: 'tinRedeem', coef: 1.0 }],
bnds: { type: glpk.GLP_UP, ub: orderState.tinRedeemOrder, lb: 0.0 },
},
{
name: 'dropInvestOrder',
vars: [{ name: 'dropInvest', coef: 1.0 }],
bnds: { type: glpk.GLP_UP, ub: orderState.dropInvestOrder, lb: 0.0 },
},
{
name: 'tinInvestOrder',
vars: [{ name: 'tinInvest', coef: 1.0 }],
bnds: { type: glpk.GLP_UP, ub: orderState.tinInvestOrder, lb: 0.0 },
},
{
name: 'maxReserve',
vars: [
{ name: 'tinRedeem', coef: -1.0 },
{ name: 'dropRedeem', coef: -1.0 },
{ name: 'tinInvest', coef: 1.0 },
{ name: 'dropInvest', coef: 1.0 },
],
bnds: { type: glpk.GLP_UP, ub: state.maxReserve - state.reserve, lb: 0.0 },
},
/**
* The next tow constraints were rewritten from the original equations in the epoch model.
* For one, minTINRatio was rewritten as a lower bound, which means both sides were multiplied by -1.
* Secondly, all output vars were moved to the left side, while all input vars were moved to the right side.
*
* E.g. for dropRedeem, in the epoch model there's both -I4*(1-B7) and +I4.
* So: -I4*(1-B7) + I4 = -0.8 I4 + 1.0 I4 = 0.2 I4 = minTinRatio * dropRedeem.
*/
{
name: 'minTINRatio',
vars: [
{ name: 'tinRedeem', coef: -(1 - state.minTinRatio) },
{ name: 'dropRedeem', coef: state.minTinRatio },
{ name: 'tinInvest', coef: 1 - state.minTinRatio },
{ name: 'dropInvest', coef: -state.minTinRatio },
],
bnds: {
type: glpk.GLP_LO,
ub: 0.0,
lb:
-(1 - state.minTinRatio) * state.netAssetValue -
(1 - state.minTinRatio) * state.reserve +
state.seniorBalance +
state.seniorDebt,
},
},
{
name: 'maxTINRatio',
vars: [
{ name: 'tinInvest', coef: -(1 - state.maxTinRatio) },
{ name: 'dropInvest', coef: state.maxTinRatio },
{ name: 'tinRedeem', coef: 1 - state.maxTinRatio },
{ name: 'dropRedeem', coef: -state.maxTinRatio },
],
bnds: {
type: glpk.GLP_LO,
ub: 0.0,
lb:
(1 - state.maxTinRatio) * state.netAssetValue +
(1 - state.maxTinRatio) * state.reserve -
state.seniorBalance -
state.seniorDebt,
},
},
],
}

const output = glpk.solve(lp, glpk.GLP_MSG_ERR)
return output.result
})
}
}
}

export type ICoordinatorActions = {
solveEpoch(): Promise<SolverSolution>
calculateOptimalSolution(state: State, orderState: OrderState): Promise<SolverResult>
}

export default CoordinatorActions

interface BaseState {
netAssetValue: number
reserve: number
seniorDebt: number
seniorBalance: number
}

export interface State extends BaseState {
netAssetValue: number
reserve: number
seniorDebt: number
seniorBalance: number
minTinRatio: number
maxTinRatio: number
maxReserve: number
}

export interface OrderState {
tinRedeemOrder: number
dropRedeemOrder: number
tinInvestOrder: number
dropInvestOrder: number
}

export interface SolverSolution {
tinRedeem: number
dropRedeem: number
tinInvest: number
dropInvest: number
}

export interface SolverResult {
z: number
status: number
vars: SolverSolution
}
5 changes: 4 additions & 1 deletion src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Collateral, { ICollateralActions } from './collateral'
import Analytics, { IAnalyticsActions } from './analytics'
import Governance, { IGovernanceActions } from './governance'
import Proxy, { IProxyActions } from './proxy'
import Coordinator, { ICoordinatorActions } from './coordinator'

export default {
Admin,
Expand All @@ -16,6 +17,7 @@ export default {
Analytics,
Governance,
Proxy,
Coordinator
}

export type TinlakeActions = IAdminActions &
Expand All @@ -26,4 +28,5 @@ export type TinlakeActions = IAdminActions &
IAnalyticsActions &
ICollateralActions &
IGovernanceActions &
IProxyActions
IProxyActions &
ICoordinatorActions
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import actions from './actions/index'
import Tinlake from './Tinlake'
const { Admin, Borrower, Lender, Analytics, Currency, Collateral, Governance, Proxy } = actions
const { Admin, Borrower, Lender, Analytics, Currency, Collateral, Governance, Proxy, Coordinator } = actions

export const TinlakeWithActions = Proxy(Borrower(Admin(Lender(Analytics(Currency(Collateral(Governance(Tinlake))))))))
export const TinlakeWithActions = Coordinator(Proxy(Borrower(Admin(Lender(Analytics(Currency(Collateral(Governance(Tinlake)))))))))
export default TinlakeWithActions

export * from './types/tinlake'
Expand Down
2 changes: 2 additions & 0 deletions src/types/declarations.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ declare module '*.abi' {

declare module 'ethjs'
declare module 'web3-utils'

declare module 'glpk.js'

0 comments on commit c4a6a54

Please sign in to comment.