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

Refinance any pair fixes #193

Merged
merged 18 commits into from
Apr 24, 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
3 changes: 0 additions & 3 deletions pnpm-lock.yaml

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

Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class SwapAction extends BaseAction {
toAsset: params.toMinimumAmount.token.address.value,
amount: params.fromAmount.toBaseUnit(),
receiveAtLeast: params.toMinimumAmount.toBaseUnit(),
fee: params.fee.toBaseUnit({ decimals: 8 }),
fee: params.fee.toBaseUnit({ decimals: 2 }),
withData: params.withData,
collectFeeFromToken: params.collectFeeInFromToken,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const SwapActionBuilder: ActionBuilder<steps.SwapStep> = async (params):

const swapData = await swapManager.getSwapDataExactInput({
chainInfo: params.user.chainInfo,
fromAmount: step.inputs.fromTokenAmount,
fromAmount: step.inputs.amountAfterFee,
toToken: step.inputs.toTokenAmount.token,
recipient: Address.createFromEthereum({ value: swapContractInfo.address as HexData }),
slippage: step.inputs.slippage,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IRefinanceParameters } from '@summerfi/sdk-common/orders'
import { ISimulation, SimulationType } from '@summerfi/sdk-common/simulation'
import { ISimulation, RefinanceSimulationTypes } from '@summerfi/sdk-common/simulation'
import { RPCClientType } from '../../rpc/SDKClient'
import { IRPCClient } from '../../interfaces/IRPCClient'

Expand All @@ -10,7 +10,7 @@ export class RefinanceSimulationManager extends IRPCClient {

public async simulateRefinancePosition(
params: IRefinanceParameters,
): Promise<ISimulation<SimulationType.Refinance>> {
): Promise<ISimulation<RefinanceSimulationTypes>> {
const refinanceParameters: IRefinanceParameters = {
sourcePosition: {
type: params.sourcePosition.type,
Expand Down
5 changes: 2 additions & 3 deletions sdk/sdk-common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@
"publish:npm": "pnpm bundle:npm && cd bundle && npm publish && cd .."
},
"dependencies": {
"bignumber.js": "9.0.1",
"superjson": "^1.13.3",
"@summerfi/common": "workspace:*"
"@summerfi/common": "workspace:*",
"superjson": "^1.13.3"
},
"devDependencies": {
"@summerfi/eslint-config": "workspace:*",
Expand Down
26 changes: 26 additions & 0 deletions sdk/sdk-common/src/common/implementation/Price.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import { BigNumber } from 'bignumber.js'
import { SerializationService } from '../../services/SerializationService'
import { CurrencySymbol } from '../enums/CurrencySymbol'
import { Token } from './Token'
import { isSameTokens } from '../utils/TokenUtils'

/**
* @class Price
* @description Represents a price of a token (baseToken) in a given currency (quoteToken)
* @description Base / Quote e.q. 2000 ETH / DAI
paszkowskiDamian marked this conversation as resolved.
Show resolved Hide resolved
* @description The financial representation (x ETH/DAI, Base/Quote) might be confusing as mathematically it is (x DAI/ETH, Quote/Base which means x DAI per 1 ETH)
* @description x amount of quoted token for one unit of base token
*/
export class Price implements IPrice {
readonly value: string
Expand Down Expand Up @@ -37,6 +41,28 @@ export class Price implements IPrice {
public toBN(): BigNumber {
return new BigNumber(this.value)
}

public hasSameQuoteToken(b: Price): boolean {
if (isToken(this.quoteToken) && isToken(b.quoteToken)) {
return isSameTokens(this.quoteToken, b.quoteToken)
}

return this.quoteToken === b.quoteToken
}

public div(b: Price) {
if (!this.hasSameQuoteToken(b)) {
throw new Error('Token bases must be the same')
}

return Price.createFrom({
value: this.toBN().div(b.toBN()).toString(),
baseToken: this.baseToken,
quoteToken: b.baseToken,
})

// TODO: case when the quotes are the same
}
}

SerializationService.registerClass(Price)
2 changes: 1 addition & 1 deletion sdk/sdk-common/src/common/implementation/TokenAmount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export class TokenAmount implements ITokenAmount {
if (multiplier instanceof Percentage) {
return new TokenAmount({
token: this.token,
amount: this.amountBN.times(multiplier.value).toString(),
amount: this.amountBN.times(multiplier.toProportion()).toString(),
})
}

Expand Down
9 changes: 9 additions & 0 deletions sdk/sdk-common/src/simulation/Enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export enum SimulationType {
Migrate = 'Migrate',
CreatePosition = 'CreatePosition',
Refinance = 'Refinance',
RefinanceDifferentPair = 'RefinanceDifPair',
RefinanceDifferentDebt = 'RefinanceDifDebt',
RefinanceDifferentCollateral = 'RefinanceDifCol',
}

export enum SimulationSteps {
Expand All @@ -30,3 +33,9 @@ export enum TokenTransferTargetType {
PositionsManager = 1,
StrategyExecutor = 0,
}

export type RefinanceSimulationTypes =
| SimulationType.Refinance
| SimulationType.RefinanceDifferentPair
| SimulationType.RefinanceDifferentCollateral
| SimulationType.RefinanceDifferentDebt
3 changes: 3 additions & 0 deletions sdk/sdk-common/src/simulation/Steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ export interface SwapStep
provider: SwapProviderType
routes: SwapRoute[]
spotPrice: Price
// Amount that will be send to our Swap service which then deducts our fee
fromTokenAmount: TokenAmount
// This amount has our fee already dedducted, and it will be send to exchange provider
amountAfterFee: TokenAmount
toTokenAmount: TokenAmount
slippage: Percentage
summerFee: Percentage
Expand Down
9 changes: 5 additions & 4 deletions sdk/sdk-common/src/utils/PercentageUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,25 @@ import { TokenAmount } from '../common/implementation/TokenAmount'

export function applyPercentage(tokenAmount: TokenAmount, percentage: Percentage): TokenAmount {
const amountBN = tokenAmount.toBN()
const newAmount = amountBN.times(percentage.value).div(100)
return TokenAmount.createFrom({ token: tokenAmount.token, amount: newAmount.toFixed(0) })
const newAmountBN = amountBN.times(percentage.value).div(100)
return TokenAmount.createFrom({ token: tokenAmount.token, amount: newAmountBN.toString() })
}

export function addPercentage(tokenAmount: TokenAmount, percentage: Percentage): TokenAmount {
const amountBN = tokenAmount.toBN()
const newAmount = amountBN.times(percentage.value).div(100)
return TokenAmount.createFrom({
token: tokenAmount.token,
amount: amountBN.plus(newAmount).toFixed(0),
amount: amountBN.plus(newAmount).toString(),
})
}

export function subtractPercentage(tokenAmount: TokenAmount, percentage: Percentage): TokenAmount {
const amountBN = tokenAmount.toBN()
const newAmount = amountBN.times(percentage.value).div(100)

return TokenAmount.createFrom({
token: tokenAmount.token,
amount: amountBN.minus(newAmount).toFixed(0),
amount: amountBN.minus(newAmount).toString(),
})
}
10 changes: 5 additions & 5 deletions sdk/sdk-e2e/tests/refinanceMakerSparkAlreadyImported.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { ProtocolName, isLendingPool } from '@summerfi/sdk-common/protocols'
import { makeSDK, type Chain, type User, Protocol } from '@summerfi/sdk-client'
import { TokenSymbol } from '@summerfi/sdk-common/common/enums'
import { IPositionsManager, IRefinanceParameters, Order } from '@summerfi/sdk-common/orders'
import { ISimulation, SimulationType } from '@summerfi/sdk-common/simulation'
import { ISimulation } from '@summerfi/sdk-common/simulation'
import { TransactionUtils } from './utils/TransactionUtils'
import {
decodeActionCalldata,
Expand Down Expand Up @@ -47,12 +47,12 @@ import {
SparkPoolId,
isSparkPoolId,
} from '@summerfi/protocol-plugins/plugins/spark'
import { RefinanceSimulationTypes } from '@summerfi/sdk-common'

jest.setTimeout(300000)

const SDKAPiUrl = 'https://zmjmtfsocb.execute-api.us-east-1.amazonaws.com/api/sdk'
const TenderlyForkUrl =
'https://virtual.mainnet.rpc.tenderly.co/d4cb5af8-8015-4342-b95d-26e5b05a6525'
const SDKAPiUrl = 'https://nkllstfoy8.execute-api.us-east-1.amazonaws.com/api/sdk'
const TenderlyForkUrl = 'https://rpc.tenderly.co/fork/50e01944-8635-4d67-9569-004d72113328'

describe.skip('Refinance Maker Spark | SDK', () => {
it('should allow refinance Maker -> Spark with same pair', async () => {
Expand Down Expand Up @@ -172,7 +172,7 @@ describe.skip('Refinance Maker Spark | SDK', () => {
makerPosition.debtAmount.token,
makerPosition.collateralAmount.token,
)
const refinanceSimulation: ISimulation<SimulationType.Refinance> =
const refinanceSimulation: ISimulation<RefinanceSimulationTypes> =
await sdk.simulator.refinance.simulateRefinancePosition({
sourcePosition: makerPosition,
targetPosition: emptyTargetPosition,
Expand Down
181 changes: 181 additions & 0 deletions sdk/sdk-e2e/tests/refinanceMakerSparkAnyPair.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import {
Percentage,
PositionId,
Token,
TokenAmount,
Position,
Address,
type Maybe,
ChainFamilyMap,
newEmptyPositionFromPool,
PositionType,
} from '@summerfi/sdk-common/common'

import { ProtocolName, isLendingPool } from '@summerfi/sdk-common/protocols'
import { makeSDK, type Chain, type User, Protocol } from '@summerfi/sdk-client'
import { TokenSymbol } from '@summerfi/sdk-common/common/enums'
import { IPositionsManager, IRefinanceParameters, Order } from '@summerfi/sdk-common/orders'
import { ISimulation } from '@summerfi/sdk-common/simulation'
import { TransactionUtils } from './utils/TransactionUtils'

import { Hex } from 'viem'
import assert from 'assert'
import { EmodeType } from '@summerfi/protocol-plugins/plugins/common'
import { ILKType, MakerPoolId } from '@summerfi/protocol-plugins/plugins/maker'
import { SparkPoolId, isSparkPoolId } from '@summerfi/protocol-plugins/plugins/spark'
import { RefinanceSimulationTypes } from '@summerfi/sdk-common'

jest.setTimeout(300000)

const SDKAPiUrl = 'https://nkllstfoy8.execute-api.us-east-1.amazonaws.com/api/sdk'
const TenderlyForkUrl =
'https://virtual.mainnet.rpc.tenderly.co/8ffae4ec-575d-40a5-87d4-295669e8a24b'

describe.skip('Refinance Maker Spark | SDK', () => {
it('should allow refinance Maker -> Spark with same pair', async () => {
// SDK
const sdk = makeSDK({ apiURL: SDKAPiUrl })

// Chain
const chain: Maybe<Chain> = await sdk.chains.getChain({
chainInfo: ChainFamilyMap.Ethereum.Mainnet,
})

assert(chain, 'Chain not found')

// User
const walletAddress = Address.createFromEthereum({
value: '0x34314adbfBb5d239bb67f0265c9c45EB8b834412',
})
const user: User = await sdk.users.getUser({
chainInfo: chain.chainInfo,
walletAddress: walletAddress,
})
expect(user).toBeDefined()
expect(user.wallet.address).toEqual(walletAddress)
expect(user.chainInfo).toEqual(chain.chainInfo)

// Positions Manager
const positionsManager: IPositionsManager = {
address: Address.createFromEthereum({
value: '0x1858b76756d19f8cb7c7756a0f96e0d7673285ed',
}),
}

// Tokens
const WETH: Maybe<Token> = await chain.tokens.getTokenBySymbol({ symbol: TokenSymbol.WETH })
assert(WETH, 'WETH not found')

const DAI: Maybe<Token> = await chain.tokens.getTokenBySymbol({ symbol: TokenSymbol.DAI })
assert(DAI, 'DAI not found')

const USDC: Maybe<Token> = await chain.tokens.getTokenBySymbol({ symbol: TokenSymbol.USDC })
assert(USDC, 'USDC not found')

const WBTC: Maybe<Token> = await chain.tokens.getTokenBySymbol({ symbol: TokenSymbol.WBTC })
assert(WBTC, 'WBTC not found')

const WSTETH: Maybe<Token> = await chain.tokens.getTokenBySymbol({ symbol: TokenSymbol.WSTETH })
assert(WSTETH, 'WSTETH not found')

const maker = await chain.protocols.getProtocol({ name: ProtocolName.Maker })
assert(maker, 'Maker protocol not found')

const makerPoolId: MakerPoolId = {
protocol: {
name: ProtocolName.Maker,
chainInfo: chain.chainInfo,
},
ilkType: ILKType.ETH_C,
vaultId: '31697',
}

const makerPool = await maker.getPool({
poolId: makerPoolId,
})
assert(makerPool, 'Maker pool not found')

if (!isLendingPool(makerPool)) {
assert(false, 'Maker pool type is not lending')
}

// Source position
const makerPosition: Position = Position.createFrom({
type: PositionType.Multiply,
positionId: PositionId.createFrom({ id: '31697' }),
debtAmount: TokenAmount.createFromBaseUnit({
token: DAI,
amount: '5000000000000000000000',
}),
collateralAmount: TokenAmount.createFromBaseUnit({
token: WETH,
amount: '100000000000000000000',
}),
pool: makerPool,
})

// Target protocol
// TODO: this should have spark protocol type so we don't need to cast, derive it from the protocol name
const spark: Maybe<Protocol> = await chain.protocols.getProtocol({
name: ProtocolName.Spark,
})
assert(spark, 'Spark not found')

const poolId: SparkPoolId = {
protocol: {
name: ProtocolName.Spark,
chainInfo: chain.chainInfo,
},
emodeType: EmodeType.None,
}

const sparkPool = await spark.getPool({
poolId,
})

assert(sparkPool, 'Pool not found')

if (!isSparkPoolId(sparkPool.poolId)) {
assert(false, 'Pool ID is not a Spark one')
}

if (!isLendingPool(sparkPool)) {
assert(false, 'Spark pool type is not lending')
}

const emptyTargetPosition = newEmptyPositionFromPool(sparkPool, DAI, WSTETH)
const refinanceSimulation: ISimulation<RefinanceSimulationTypes> =
await sdk.simulator.refinance.simulateRefinancePosition({
sourcePosition: makerPosition,
targetPosition: emptyTargetPosition,
slippage: Percentage.createFrom({ value: 0.2 }),
} as IRefinanceParameters)

expect(refinanceSimulation).toBeDefined()

expect(refinanceSimulation.sourcePosition?.positionId).toEqual(makerPosition.positionId)
expect(refinanceSimulation.targetPosition.pool.poolId).toEqual(sparkPool.poolId)

const refinanceOrder: Maybe<Order> = await user.newOrder({
positionsManager,
simulation: refinanceSimulation,
})

assert(refinanceOrder, 'Order not found')

// Send transaction
console.log('Sending transaction...')

const privateKey = process.env.DEPLOYER_PRIVATE_KEY as Hex
const transactionUtils = new TransactionUtils({
rpcUrl: TenderlyForkUrl,
walletPrivateKey: privateKey,
})

const receipt = await transactionUtils.sendTransaction({
transaction: refinanceOrder.transactions[0].transaction,
})

console.log('Transaction sent:', receipt)
})
})
Loading
Loading