Skip to content
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
39 changes: 21 additions & 18 deletions contracts/protocol/modules/v2/PerpV2BasisTradingModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ contract PerpV2BasisTradingModule is PerpV2LeverageModuleV2 {

if (positions[_setToken].length > 0) {

uint256 updatedSettledFunding = _getUpdatedSettledFunding(_setToken);
uint256 updatedSettledFunding = getUpdatedSettledFunding(_setToken);

int256 newExternalPositionUnit = _executePositionTrades(_setToken, _setTokenQuantity, false, true);

Expand All @@ -439,31 +439,18 @@ contract PerpV2BasisTradingModule is PerpV2LeverageModuleV2 {
return _formatAdjustments(_setToken, newExternalPositionUnitNetFees);
}

/* ============ Internal Functions ============ */

/**
* @dev Updates tracked settled funding. Once funding is settled to `owedRealizedPnl` on Perpetual protocol, it is difficult to
* extract out the funding value again on-chain. This function is called in an external function and is used to track and store
* pending funding payment that is about to be settled due to subsequent logic in the external function.
*
* @param _setToken Instance of SetToken
* @return uint256 Returns the updated settled funding value
*/
function _updateSettledFunding(ISetToken _setToken) internal returns (uint256) {
uint256 newSettledFunding = _getUpdatedSettledFunding(_setToken);
settledFunding[_setToken] = newSettledFunding;
return newSettledFunding;
}

/**
* @dev Adds pending funding payment to tracked settled funding. Returns updated settled funding value.
*
* NOTE: Tracked settled funding value can not be less than zero, hence it is reset to zero if pending funding
* payment is negative and |pending funding payment| >= |settledFunding[_setToken]|.
*
* NOTE: Returned updated settled funding value is correct only for the current block since pending funding payment
* updates every block.
*
* @param _setToken Instance of SetToken
*/
function _getUpdatedSettledFunding(ISetToken _setToken) internal view returns (uint256) {
function getUpdatedSettledFunding(ISetToken _setToken) public view returns (uint256) {
// NOTE: pendingFundingPayments are represented as in the Perp system as "funding owed"
// e.g a positive number is a debt which gets subtracted from owedRealizedPnl on settlement.
// We are flipping its sign here to reflect its settlement value.
Expand All @@ -480,6 +467,22 @@ contract PerpV2BasisTradingModule is PerpV2LeverageModuleV2 {
return 0;
}

/* ============ Internal Functions ============ */

/**
* @dev Updates tracked settled funding. Once funding is settled to `owedRealizedPnl` on Perpetual protocol, it is difficult to
* extract out the funding value again on-chain. This function is called in an external function and is used to track and store
* pending funding payment that is about to be settled due to subsequent logic in the external function.
*
* @param _setToken Instance of SetToken
* @return uint256 Returns the updated settled funding value
*/
function _updateSettledFunding(ISetToken _setToken) internal returns (uint256) {
uint256 newSettledFunding = getUpdatedSettledFunding(_setToken);
settledFunding[_setToken] = newSettledFunding;
return newSettledFunding;
}

/**
* @dev Calculates manager and protocol fees on withdrawn funding amount and transfers them to
* their respective recipients (in USDC).
Expand Down
131 changes: 131 additions & 0 deletions test/protocol/modules/v2/perpV2BasisTradingModule.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1916,6 +1916,137 @@ describe("PerpV2BasisTradingModule", () => {
});
});

describe("#getUpdatedSettledFunding", async () => {
let setToken: SetToken;
let subjectSetToken: Address;

const initializeContracts = async () => {
const depositQuantity = usdcUnits(10);

setToken = await issueSetsAndDepositToPerp(depositQuantity, true);

await perpBasisTradingModule.connect(owner.wallet).trade(
setToken.address,
vETH.address,
ether(0.5),
ether(5.15)
);

// Move oracle price up and wait one day
await perpSetup.setBaseTokenOraclePrice(vETH, usdcUnits(10.2));
await increaseTimeAsync(ONE_DAY_IN_SECONDS);

await perpBasisTradingModule.connect(owner.wallet).tradeAndTrackFunding(
setToken.address,
vETH.address,
ether(0.5),
ether(5.15),
);
};

const initializeSubjectVariables = async () => {
subjectSetToken = setToken.address;
};

cacheBeforeEach(initializeContracts);
beforeEach(initializeSubjectVariables);

async function subject(): Promise<BigNumber> {
return await perpBasisTradingModule.getUpdatedSettledFunding(subjectSetToken);
}

describe("when pending funding payment is zero", async () => {
it("should return current settled funding", async () => {
const settledFundingBefore = await perpBasisTradingModule.settledFunding(subjectSetToken);
const updatedSettledFunding = await subject();

expect(settledFundingBefore).to.be.gt(ZERO);
expect(updatedSettledFunding).to.eq(settledFundingBefore);
});
});

describe("when pending funding payment is positive", async () => {
beforeEach(async () => {
// Move oracle price up and wait one day
await perpSetup.setBaseTokenOraclePrice(vETH, usdcUnits(10.5));
await increaseTimeAsync(ONE_DAY_IN_SECONDS);
});

it("should return correct updated settled funding", async () => {
const settledFundingBefore = await perpBasisTradingModule.settledFunding(subjectSetToken);
const pendingFundingPayment = (await perpSetup.exchange.getAllPendingFundingPayment(subjectSetToken)).mul(-1);
const expectedSettledFunding = settledFundingBefore.add(pendingFundingPayment);

const updatedSettledFunding = await subject();

expect(pendingFundingPayment).to.be.gt(ZERO);
expect(updatedSettledFunding).to.be.gt(settledFundingBefore);
expect(updatedSettledFunding).to.eq(expectedSettledFunding);
});
});

describe("when pending funding payment is negative", async () => {
describe("when absolute settled funding is less than absolute negative funding", async () => {
beforeEach(async () => {
// Move oracle price down and wait one day
await perpSetup.setBaseTokenOraclePrice(vETH, usdcUnits(9.5));
await increaseTimeAsync(ONE_DAY_IN_SECONDS);
});

it("verify testing conditions", async () => {
const settledFundingBefore = await perpBasisTradingModule.settledFunding(subjectSetToken);
const pendingFundingBefore = await perpSetup.exchange.getAllPendingFundingPayment(subjectSetToken);

expect(pendingFundingBefore.abs()).to.be.gt(settledFundingBefore);
});

it("should return correct updated settled funding (zero)", async () => {
const updatedSettledFunding = await subject();
expect(updatedSettledFunding).to.eq(ZERO);
});
});

describe("when absolute settled funding is greater then absolute negative pending funding", async () => {
beforeEach(async () => {
// Move oracle price up and wait one day
await perpSetup.setBaseTokenOraclePrice(vETH, usdcUnits(11));
await increaseTimeAsync(ONE_DAY_IN_SECONDS);

// Trade to accrue pending funding to tracked settled funding
await perpBasisTradingModule.connect(owner.wallet).tradeAndTrackFunding(
setToken.address,
vETH.address,
ether(1),
ether(10.15)
);

// Move oracle price down and wait one day
await perpSetup.setBaseTokenOraclePrice(vETH, usdcUnits(9.8));
await increaseTimeAsync(ONE_DAY_IN_SECONDS);
});

it("verify testing conditions", async () => {
const settledFundingBefore = await perpBasisTradingModule.settledFunding(subjectSetToken);
const pendingFundingBefore = await perpSetup.exchange.getAllPendingFundingPayment(subjectSetToken);

expect(settledFundingBefore.abs()).to.be.gt(pendingFundingBefore.abs());
});

it("should return correct updated settled funding", async () => {
const settledFundingBefore = await perpBasisTradingModule.settledFunding(subjectSetToken);
const pendingFundingPayment = (await perpSetup.exchange.getAllPendingFundingPayment(subjectSetToken)).mul(-1);
const expectedSettledFunding = settledFundingBefore.add(pendingFundingPayment);

const updatedSettledFunding = await subject();

expect(pendingFundingPayment).to.be.lt(ZERO);
expect(updatedSettledFunding).to.be.lt(settledFundingBefore);
expect(updatedSettledFunding).to.eq(expectedSettledFunding);
});
});
});
});

describe("#updateFeeRecipient", async () => {
let setToken: SetToken;
let isInitialized: boolean;
Expand Down