Skip to content

Commit

Permalink
Merge pull request #99 from gammaswap/feat/upd-v1core1.2.1
Browse files Browse the repository at this point in the history
publish package
  • Loading branch information
0xDanr authored Mar 2, 2024
2 parents 9ba5527 + 9d35ee2 commit e4bc9b1
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 10 deletions.
43 changes: 43 additions & 0 deletions contracts/PositionManagerExternalWithStaking.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import "@gammaswap/v1-core/contracts/interfaces/IGammaPoolExternal.sol";

import "./interfaces/IPositionManagerExternal.sol";
import "./PositionManagerWithStaking.sol";

/// @title PositionManagerExternalWithStaking, concrete implementation of IPositionManagerExternal
/// @author Daniel D. Alcarraz (https://github.com/0xDanr)
/// @notice Inherits PositionManager functionality from PositionManagerWithStaking and defines functionality to rebalance
/// @notice loan collateral using external contracts by calling GammaPool::rebalanceExternally()
contract PositionManagerExternalWithStaking is PositionManagerWithStaking, IPositionManagerExternal {

/// @dev Constructs the PositionManagerWithStaking contract.
/// @param _factory Address of the contract factory.
/// @param _WETH Address of the Wrapped Ether (WETH) contract.
constructor(address _factory, address _WETH) PositionManagerWithStaking(_factory, _WETH) {}

/// @dev Flash loan pool's collateral and/or lp tokens to external address. Rebalanced loan collateral is acceptable
/// @dev in repayment of flash loan. Function can be used for other purposes besides rebalancing collateral.
/// @param gammaPool - address of GammaPool of the loan
/// @param tokenId - unique id identifying loan
/// @param amounts - collateral amounts being flash loaned
/// @param lpTokens - amount of CFMM LP tokens being flash loaned
/// @param to - address that will receive flash loan swaps and potentially rebalance loan's collateral
/// @param data - optional bytes parameter for custom user defined data
/// @param minCollateral - minimum amount of expected collateral after re-balancing. Used for slippage control
/// @return loanLiquidity - updated loan liquidity, includes flash loan fees
/// @return tokensHeld - updated collateral token amounts backing loan
function rebalanceCollateralExternally(address gammaPool, uint256 tokenId, uint128[] memory amounts, uint256 lpTokens, address to, bytes calldata data, uint128[] memory minCollateral) internal virtual returns(uint256 loanLiquidity, uint128[] memory tokensHeld) {
(loanLiquidity, tokensHeld) = IGammaPoolExternal(gammaPool).rebalanceExternally(tokenId, amounts, lpTokens, to, data);
checkMinCollateral(tokensHeld, minCollateral);
emit RebalanceCollateralExternally(gammaPool, tokenId, loanLiquidity, tokensHeld);
}

/// @dev See {IPositionManagerExternal-rebalanceCollateralExternally}.
function rebalanceCollateralExternally(RebalanceCollateralExternallyParams calldata params) external virtual override isAuthorizedForToken(params.tokenId) isExpired(params.deadline) returns(uint256 loanLiquidity, uint128[] memory tokensHeld) {
address gammaPool = getGammaPoolAddress(params.cfmm, params.protocolId);
(loanLiquidity,tokensHeld) = rebalanceCollateralExternally(gammaPool, params.tokenId, params.amounts, params.lpTokens, params.to, params.data, params.minCollateral);
_logPrice(gammaPool);
}
}
47 changes: 47 additions & 0 deletions contracts/interfaces/IPositionManagerExternal.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import "./IPositionManager.sol";

/// @title Interface for PositionManagerExternal
/// @author Daniel D. Alcarraz (https://github.com/0xDanr)
/// @notice Defines external functions and events emitted by PositionManagerExternal
/// @dev Interface also defines all GammaPool events through inheritance of IGammaPool and IGammaPoolEvents
interface IPositionManagerExternal is IPositionManager {

/// @dev Emitted when re-balancing a loan's collateral amounts (swapping one collateral token for another) using an external contract
/// @param pool - loan's pool address
/// @param tokenId - id identifying loan in pool
/// @param loanLiquidity - liquidity borrowed in invariant terms
/// @param tokensHeld - new loan collateral amounts
event RebalanceCollateralExternally(address indexed pool, uint256 tokenId, uint256 loanLiquidity, uint128[] tokensHeld);

/// @dev Struct parameters for `rebalanceCollateralExternally` function.
struct RebalanceCollateralExternallyParams {
/// @dev protocolId of GammaPool (e.g. version of GammaPool)
uint16 protocolId;
/// @dev address of CFMM, along with protocolId can be used to calculate GammaPool address
address cfmm;
/// @dev tokenId of loan whose collateral will change
uint256 tokenId;
/// @dev amounts of reserve tokens to swap (>0 buy token, <0 sell token). At least one index value must be set to zero
uint128[] amounts;
/// @dev CFMM LP tokens requesting to borrow during external rebalancing. Must be returned at function call end
uint256 lpTokens;
/// @dev address of contract that will rebalance collateral. This address must return collateral back to GammaPool
address to;
/// @param data - optional bytes parameter for custom user defined data
bytes data;
/// @dev timestamp after which the transaction expires. Used to prevent stale transactions from executing
uint256 deadline;
/// @dev minimum amounts of collateral expected to have after re-balancing collateral. Slippage protection
uint128[] minCollateral;
}

/// @dev Re-balance loan collateral tokens by swapping one for another using an external source
/// @param params - struct containing params to identify a GammaPool and loan with information to re-balance its collateral
/// @return loanLiquidity - updated loan liquidity, includes flash loan fees
/// @return tokensHeld - new loan collateral token amounts
function rebalanceCollateralExternally(RebalanceCollateralExternallyParams calldata params) external returns(uint256 loanLiquidity, uint128[] memory tokensHeld);

}
40 changes: 40 additions & 0 deletions contracts/test/TestGammaPoolExternal.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "@gammaswap/v1-core/contracts/interfaces/IGammaPoolExternal.sol";
import "./TestGammaPool2.sol";

contract TestGammaPoolExternal is TestGammaPool2, IGammaPoolExternal {

struct params {
uint256 num1;
address to;
}

address immutable public override externalRebalanceStrategy;
address immutable public override externalLiquidationStrategy;

constructor(uint16 protocolId_, address factory_, address borrowStrategy_, address repayStrategy_, address rebalanceStrategy_,
address shortStrategy_, address singleLiquidationStrategy_, address batchLiquidationStrategy_, address viewer_,
address externalRebalanceStrategy_, address externalLiquidationStrategy_) TestGammaPool2(protocolId_, factory_,
borrowStrategy_, repayStrategy_, rebalanceStrategy_, shortStrategy_, singleLiquidationStrategy_, batchLiquidationStrategy_,
viewer_) {
externalRebalanceStrategy = externalRebalanceStrategy_;
externalLiquidationStrategy = externalLiquidationStrategy_;
}

function rebalanceExternally(uint256 tokenId, uint128[] calldata amounts, uint256 lpTokens, address to, bytes calldata data) external virtual override returns(uint256 loanLiquidity, uint128[] memory tokensHeld) {
params memory _params = abi.decode(data, (params));
tokensHeld = new uint128[](2);
tokensHeld[0] = uint128(amounts[0] + 10);
tokensHeld[1] = uint128(amounts[1] + 20);
loanLiquidity = uint256(uint160(_params.to)) + _params.num1 + lpTokens;
}

function liquidateExternally(uint256 tokenId, uint128[] calldata amounts, uint256 lpTokens, address to, bytes calldata data) external virtual override returns(uint256 loanLiquidity, uint256[] memory refund) {
refund = new uint256[](2);
refund[0] = uint256(amounts[0]) + 30;
refund[1] = uint256(amounts[1]) + 40;
return(lpTokens, refund);
}
}
6 changes: 3 additions & 3 deletions contracts/test/TestPositionManager.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.21;

import "../PositionManager.sol";
import "../PositionManagerExternalWithStaking.sol";

contract TestPositionManager is PositionManager {
contract TestPositionManager is PositionManagerExternalWithStaking {

constructor(address _factory, address _WETH) PositionManager( _factory, _WETH) {}
constructor(address _factory, address _WETH) PositionManagerExternalWithStaking( _factory, _WETH) {}

function createTestLoan(address to) external virtual returns(uint256 tokenId) {
tokenId = 1;
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gammaswap/v1-periphery",
"version": "1.1.20",
"version": "1.2.0",
"description": "Periphery contracts for the GammaSwap V1 protocol",
"homepage": "https://gammaswap.com",
"scripts": {
Expand Down Expand Up @@ -67,7 +67,7 @@
"typescript": "^4.7.4"
},
"dependencies": {
"@gammaswap/v1-core": "^1.1.27",
"@gammaswap/v1-core": "^1.2.1",
"@openzeppelin/contracts": "^4.7.0"
}
}
63 changes: 62 additions & 1 deletion test/PositionManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { ethers } from "hardhat";
import { expect } from "chai";

const PROTOCOL_ID = 10000;
const PROTOCOL_ID_EXTERNAL = 10001;

describe("PositionManager", function () {
let TestERC20: any;
let TestPoolViewer: any;
let GammaPool: any;
let GammaPool2: any;
let GammaPoolExternal: any;
let GammaPoolFactory: any;
let TestPositionManager: any;
let PositionManagerQueries: any;
Expand All @@ -31,14 +33,17 @@ describe("PositionManager", function () {
let gammaPool: any;
let gammaPool2: any;
let gammaPool3: any;
let gammaPool4: any;
let poolViewer: any;
let cfmm: any;
let cfmm2: any;
let cfmm3: any;
let protocolId: any;
let protocolIdExternal: any;
let gammaPoolAddr: any;
let gammaPoolAddr2: any;
let gammaPoolAddr3: any;
let gammaPoolAddr4: any;
let tokenId: any;

// `beforeEach` will run before each test, re-deploying the contract every
Expand All @@ -53,6 +58,7 @@ describe("PositionManager", function () {
PriceDataQueries = await ethers.getContractFactory("PriceDataQueries");
GammaPool = await ethers.getContractFactory("TestGammaPool");
GammaPool2 = await ethers.getContractFactory("TestGammaPool2");
GammaPoolExternal = await ethers.getContractFactory("TestGammaPoolExternal");
[owner, addr1, addr2, addr3, addr4, addr5, addr6, addr7, addr8] = await ethers.getSigners();

// To deploy our contract, we just have to call Token.deploy() and await
Expand All @@ -76,9 +82,14 @@ describe("PositionManager", function () {
const blocksPerYear = 60 * 60 * 24 * 365 / 12; // assumes 12 seconds per block
priceStore = await PriceDataQueries.deploy(blocksPerYear, owner.address, maxLen, frequency);

const implementation = await GammaPool.deploy(PROTOCOL_ID, factory.address, addr1.address, addr2.address, addr3.address, addr5.address, addr6.address, addr7.address, poolViewer.address);
const implementation = await GammaPool.deploy(PROTOCOL_ID, factory.address, addr1.address, addr2.address, addr3.address,
addr5.address, addr6.address, addr7.address, poolViewer.address);

const implementationExternal = await GammaPoolExternal.deploy(PROTOCOL_ID_EXTERNAL, factory.address, addr1.address, addr2.address,
addr3.address, addr4.address, addr5.address, addr6.address, poolViewer.address, addr7.address, addr8.address);

await (await factory.addProtocol(implementation.address)).wait();
await (await factory.addProtocol(implementationExternal.address)).wait();

posMgr = await TestPositionManager.deploy(factory.address, WETH.address);
await posMgr.initialize(store.address, priceStore.address)
Expand All @@ -93,13 +104,20 @@ describe("PositionManager", function () {
tokens: [tokenA.address, tokenB.address]
};

const createPoolParamsExternal = {
cfmm: cfmm.address,
protocolId: PROTOCOL_ID_EXTERNAL,
tokens: [tokenA.address, tokenB.address]
};

const data = ethers.utils.defaultAbiCoder.encode(
[],
[]
);
const res = await (await factory.createPool(createPoolParams.protocolId, createPoolParams.cfmm ,createPoolParams.tokens, data)).wait();
const res2 = await (await factory.createPool(createPoolParams.protocolId, cfmm2.address ,createPoolParams.tokens, data)).wait();
const res3 = await (await factory.createPool(createPoolParams.protocolId, cfmm3.address ,createPoolParams.tokens, data)).wait();
const res4 = await (await factory.createPool(createPoolParamsExternal.protocolId, createPoolParamsExternal.cfmm ,createPoolParamsExternal.tokens, data)).wait();

const { args } = res.events[1];
gammaPoolAddr = args.pool;
Expand All @@ -109,6 +127,11 @@ describe("PositionManager", function () {

protocolId = args.protocolId;

const { args: args4 } = res4.events[1];
gammaPoolAddr4 = args4.pool;

protocolIdExternal = args4.protocolId;

gammaPool = await GammaPool.attach(
gammaPoolAddr // The deployed contract address
);
Expand All @@ -124,6 +147,12 @@ describe("PositionManager", function () {
);
await gammaPool3.approve(posMgr.address, ethers.constants.MaxUint256);


gammaPool4 = await GammaPoolExternal.attach(
gammaPoolAddr4 // The deployed contract address
);
await gammaPool4.approve(posMgr.address, ethers.constants.MaxUint256);

const { events } = await (await posMgr.createTestLoan(owner.address)).wait();
tokenId = events[0].args.tokenId;
});
Expand Down Expand Up @@ -815,6 +844,38 @@ describe("PositionManager", function () {
expect(args.tokensHeld.length).to.equal(2);
});

it("#rebalanceCollateralExternally should return tokenId and length of tokens held", async function () {
const lpTokens = 3;
const dataNum = 77;
const dataAddr = addr5.address;
const data = ethers.utils.defaultAbiCoder.encode(
["uint256","address"],
[dataNum,dataAddr]
);
const RebalanceCollateralParams = {
cfmm: cfmm.address,
protocolId: protocolIdExternal,
tokenId: tokenId,
amounts: [4, 2],
lpTokens: lpTokens,
to: addr4.address,
data: data,
deadline: ethers.constants.MaxUint256,
minCollateral: [0,0]
}

const expectedLoanLiquidityNum = ethers.BigNumber.from(dataAddr.toLowerCase()).add(dataNum).add(lpTokens);
const res = await (await posMgr.rebalanceCollateralExternally(RebalanceCollateralParams)).wait();

const { args } = res.events[0];
expect(args.pool).to.equal(gammaPool4.address);
expect(args.tokenId.toNumber()).to.equal(tokenId);
expect(args.tokensHeld.length).to.equal(2);
expect(args.tokensHeld[0]).to.equal(14);
expect(args.tokensHeld[1]).to.equal(22);
expect(args.loanLiquidity).to.equal(expectedLoanLiquidityNum);
});

it("#createLoanBorrowAndRebalance should return tokenId, tokensHeld, amounts. No deltas", async function () {
await tokenA.approve(posMgr.address, ethers.constants.MaxUint256); // must approve before sending tokens
await tokenB.approve(posMgr.address, ethers.constants.MaxUint256); // must approve before sending tokens
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -576,10 +576,10 @@
resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.0.0.tgz#f22824caff3ae506b18207bad4126dbc6ccdb6b8"
integrity sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==

"@gammaswap/v1-core@^1.1.27":
version "1.1.27"
resolved "https://npm.pkg.github.com/download/@gammaswap/v1-core/1.1.27/58e6196c4946a5808628254ecd3d52a09065624d#58e6196c4946a5808628254ecd3d52a09065624d"
integrity sha512-+Y2pD4j01a6n8QUPRyaIGGuhu4O+U5AfMrki/FYxWHey00S/V2hhimcfTkkS1lcmd95pBYmmOxcmNYpX5MU1og==
"@gammaswap/v1-core@^1.2.1":
version "1.2.1"
resolved "https://npm.pkg.github.com/download/@gammaswap/v1-core/1.2.1/c5a65f759c77ba9f03e3ab86376771bec1edc2d5#c5a65f759c77ba9f03e3ab86376771bec1edc2d5"
integrity sha512-xTraMCQS0W4ZCU0e3v1mCdJ5PkLFdR2d3SAthgZ8DYpmiHe0QjJrgStfRl+I1UprgPlVp0qQygzFiaSeZXGYzQ==
dependencies:
"@openzeppelin/contracts" "^4.7.0"

Expand Down

0 comments on commit e4bc9b1

Please sign in to comment.