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

[fix-exit-queue] v1/v2 support logic #86

Merged
merged 1 commit into from
Mar 29, 2024
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
8 changes: 4 additions & 4 deletions src/contracts/abis/VaultAbi.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,17 +131,17 @@
"outputs": [
{
"internalType": "uint256",
"name": "leftShares",
"name": "leftTickets",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "claimedShares",
"name": "exitedTickets",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "claimedAssets",
"name": "exitedAssets",
"type": "uint256"
}
],
Expand Down Expand Up @@ -454,4 +454,4 @@
"stateMutability": "nonpayable",
"type": "function"
}
]
]
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ describe('parseExitRequests function', () => {

const input: ParseExitRequestsInput = {
contracts,
totalShares: 1000n,
options: { network },
userAddress: ZeroAddress,
vaultAddress: ZeroAddress,
Expand All @@ -34,13 +33,21 @@ describe('parseExitRequests function', () => {
{
positionTicket: 'positionTicket-1',
timestamp: '123456',
totalAssets: '0',
totalShares: '100',
},
{
positionTicket: 'positionTicket-2',
timestamp: '123456',
timestamp: '123457',
totalAssets: '0',
totalShares: '200',
},
{
positionTicket: 'positionTicket-2',
timestamp: '123458',
totalAssets: '300',
totalShares: '0',
},
],
}

Expand All @@ -53,17 +60,23 @@ describe('parseExitRequests function', () => {
.mockResolvedValueOnce([
[ 1n ],
[ 2n ],
[ 3n ],
])
.mockResolvedValueOnce([
{
claimedAssets: 30n,
claimedShares: 50n,
leftShares: 10n,
exitedAssets: 30n,
exitedTickets: 50n,
leftTickets: 10n,
},
{
exitedAssets: 1n,
exitedTickets: 100n,
leftTickets: 20n,
},
{
claimedAssets: 1n,
claimedShares: 100n,
leftShares: 20n,
exitedAssets: 250n,
exitedTickets: 200n,
leftTickets: 30n,
},
])
.mockResolvedValueOnce([
Expand All @@ -79,16 +92,24 @@ describe('parseExitRequests function', () => {
{
exitQueueIndex: 1n,
timestamp: '123456',
isV1Position: true,
positionTicket: 'positionTicket-1',
},
{
exitQueueIndex: 2n,
timestamp: '123456',
timestamp: '123457',
isV1Position: true,
positionTicket: 'positionTicket-2',
},
{
exitQueueIndex: 3n,
timestamp: '123458',
isV1Position: false,
positionTicket: 'positionTicket-2',
},
],
total: 131n,
withdrawable: 31n,
total: 431n,
withdrawable: 281n,
})
})

Expand Down Expand Up @@ -138,12 +159,13 @@ describe('parseExitRequests function', () => {
.mockResolvedValueOnce([
[ -1n ],
[ 1n ],
[ -1n ],
])
.mockResolvedValueOnce([
{
leftShares: 10n,
claimedShares: 50n,
claimedAssets: 30n,
leftTickets: 10n,
exitedTickets: 50n,
exitedAssets: 30n,
},
])
.mockResolvedValueOnce([ { assets: 50n } ])
Expand All @@ -153,10 +175,11 @@ describe('parseExitRequests function', () => {
expect(result).toEqual({
positions: [ {
exitQueueIndex: 1n,
timestamp: '123456',
timestamp: '123457',
isV1Position: true,
positionTicket: 'positionTicket-2',
} ],
total: 80n,
total: 380n,
withdrawable: 30n,
})
})
Expand All @@ -166,10 +189,12 @@ describe('parseExitRequests function', () => {
.mockResolvedValueOnce([
[ 0n ],
[ 1n ],
[ 1n ],
])
.mockResolvedValueOnce([
{ leftShares: 0n, claimedShares: 0n, claimedAssets: 0n },
{ leftShares: 0n, claimedShares: 0n, claimedAssets: 0n },
{ leftTickets: 0n, exitedTickets: 100n, exitedAssets: 101n },
{ leftTickets: 0n, exitedTickets: 200n, exitedAssets: 202n },
{ leftTickets: 0n, exitedTickets: 300n, exitedAssets: 300n },
])
.mockResolvedValueOnce([ { assets: 0n } ])

Expand All @@ -180,47 +205,24 @@ describe('parseExitRequests function', () => {
{
exitQueueIndex: 0n,
timestamp: '123456',
isV1Position: true,
positionTicket: 'positionTicket-1',
},
{
exitQueueIndex: 1n,
timestamp: '123456',
timestamp: '123457',
isV1Position: true,
positionTicket: 'positionTicket-2',
},
],
total: 0n,
withdrawable: 0n,
})
})

it('should handle remainingShares being 0', async () => {
(vaultMulticall as jest.Mock)
.mockResolvedValueOnce([
[ 0n ],
[ 1n ],
])
.mockResolvedValueOnce([
{ leftShares: 10n, claimedShares: 1000n, claimedAssets: 30n },
])
.mockResolvedValueOnce([])

const result = await parseExitRequests(input)

expect(result).toEqual({
positions: [
{
exitQueueIndex: 0n,
timestamp: '123456',
positionTicket: 'positionTicket-1',
},
{
exitQueueIndex: 1n,
timestamp: '123456',
timestamp: '123458',
isV1Position: false,
positionTicket: 'positionTicket-2',
},
],
total: 30n,
withdrawable: 30n,
total: 603n,
withdrawable: 603n,
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,32 @@ export type ParseExitRequestsInput = {
provider: StakeWise.Provider
options: StakeWise.Options
userAddress: string
totalShares: bigint
vaultAddress: string
exitRequests: Array<{
positionTicket: string
totalShares: string
totalAssets: string
timestamp: string
}>
}

type Position = {
exitQueueIndex: bigint
positionTicket: string
isV1Position: boolean
timestamp: string
}

type ParseExitRequestsOutput = {
export type ParseExitRequestsOutput = {
total: bigint
withdrawable: bigint
positions: Position[]
}

type ExitedAssetsResponse = Array<{
leftShares: bigint
claimedShares: bigint
claimedAssets: bigint
leftTickets: bigint
exitedTickets: bigint
exitedAssets: bigint
}>

const _checkTimestamp = async (timestamp: string, provider: StakeWise.Provider) => {
Expand All @@ -46,7 +47,7 @@ const _checkTimestamp = async (timestamp: string, provider: StakeWise.Provider)
}

const parseExitRequests = async (values: ParseExitRequestsInput): Promise<ParseExitRequestsOutput> => {
const { options, contracts, provider, userAddress, vaultAddress, totalShares, exitRequests } = values
const { options, contracts, provider, userAddress, vaultAddress, exitRequests } = values

const keeperContract = contracts.base.keeper
const vaultContract = contracts.helpers.createVault(vaultAddress)
Expand Down Expand Up @@ -75,78 +76,94 @@ const parseExitRequests = async (values: ParseExitRequestsInput): Promise<ParseE
const claims: Position[] = []
const indexes = (indexesResponse || [])

let queuedShares = 0n,
queuedAssets = 0n

for (let i = 0; i < indexes.length; i++) {
const { positionTicket, timestamp, totalShares, totalAssets } = exitRequests[i]

queuedShares += BigInt(totalShares || 0)
queuedAssets += BigInt(totalAssets || 0)

// If the index is -1 then we cannot claim anything. Otherwise, the value is >= 0.
const exitQueueIndex = indexes[i][0]
const { positionTicket, timestamp } = exitRequests[i]

if (exitQueueIndex < 0n) {
continue
}

// 24 hours must have elapsed since the position was created
const is24HoursPassed = await _checkTimestamp(timestamp, provider)

// If the index is -1 then we cannot claim anything. Otherwise, the value is >= 0.
const isValid = exitQueueIndex > -1n

if (isValid && is24HoursPassed) {
const item = { exitQueueIndex, positionTicket, timestamp }
if (is24HoursPassed) {
const isV1Position = BigInt(totalShares) > 0
const item = { exitQueueIndex, positionTicket, timestamp, isV1Position }

claims.push(item)
}
}

let exitedAssetsResponse: ExitedAssetsResponse = []

if (claims.length) {
// We need to get the data of the contract after the claim.
exitedAssetsResponse = await vaultMulticall<ExitedAssetsResponse>({
...commonMulticallParams,
request: {
params: claims.map(({ positionTicket, exitQueueIndex, timestamp }) => ({
method: 'calculateExitedAssets',
args: [ userAddress, positionTicket, timestamp, exitQueueIndex ],
})),
callStatic: true,
},
}) || []
}
else {
if (!claims.length) {
const result = await vaultMulticall<Array<{ assets: bigint }>>({
...commonMulticallParams,
request: {
params: [ { method: 'convertToAssets', args: [ totalShares ] } ],
params: [ { method: 'convertToAssets', args: [ queuedShares ] } ],
callStatic: true,
},
})
const totalV1QueuedAssets = result[0]?.assets || 0n

// If there are no positions with an index greater than 0 or their timestamp has failed the 24 hour check.
// If there are no positions with an index greater than 0 or their timestamp has failed the 24-hour check.
// Then we can use totalShares from the subgraph to show total
return {
positions: [],
withdrawable: 0n,
total: result[0]?.assets || 0n,
total: totalV1QueuedAssets + queuedAssets,
}
}

let withdrawableAssets = 0n,
totalLeftShares = 0n,
totalLeftAssets = 0n
// We need to calculate the exited assets for every position.
const exitedAssetsResponse = await vaultMulticall<ExitedAssetsResponse>({
...commonMulticallParams,
request: {
params: claims.map(({ positionTicket, exitQueueIndex, timestamp }) => ({
method: 'calculateExitedAssets',
args: [ userAddress, positionTicket, timestamp, exitQueueIndex ],
})),
callStatic: true,
},
}) || []

// Calculate total withdrawable assets
let withdrawableAssets = 0n

exitedAssetsResponse.forEach(({ exitedTickets, exitedAssets }, i) => {
const { isV1Position } = claims[i]

if (isV1Position) {
// in V1 exit queue exit tickets are shares
queuedShares -= BigInt(exitedTickets || 0)
}
else {
queuedAssets -= BigInt(exitedAssets || 0)
}

exitedAssetsResponse.forEach(({ leftShares, claimedAssets }) => {
totalLeftShares += leftShares
withdrawableAssets += claimedAssets
withdrawableAssets += BigInt(exitedAssets || 0)
})

if (totalLeftShares > 0) {
if (queuedShares > 0) {
const result = await vaultMulticall<Array<{ assets: bigint }>>({
...commonMulticallParams,
request: {
params: [ { method: 'convertToAssets', args: [ totalLeftShares ] } ],
params: [ { method: 'convertToAssets', args: [ queuedShares ] } ],
callStatic: true,
},
})

totalLeftAssets = result[0]?.assets || 0n
queuedAssets += result[0]?.assets || 0n
}

const total = withdrawableAssets + totalLeftAssets
const total = withdrawableAssets + queuedAssets

return {
total,
Expand Down
Loading