From 358b9bfebb0e9ee594090d0f81a164fd5231d060 Mon Sep 17 00:00:00 2001
From: Pedro <pedroantonioppms@gmail.com>
Date: Tue, 19 Oct 2021 18:26:39 -0400
Subject: [PATCH 1/2] Added Pangolin Exchange Connector

Fixed Contract

Add: test to Pangolin connector

Fix InstaDapp Addresses/Scripts/PNG Connector

Removed unnecessary scripts

Moved PNG connector contract and Connector tests
---
 .../connectors/pangolin/exchange/events.sol   |  41 +++
 .../connectors/pangolin/exchange/helpers.sol  | 146 +++++++++
 .../pangolin/exchange/interface.sol           | 164 ++++++++++
 .../connectors/pangolin/exchange/main.sol     | 196 ++++++++++++
 test/avalanche/pangolin/pangolin.test.ts      | 283 ++++++++++++++++++
 5 files changed, 830 insertions(+)
 create mode 100644 contracts/avalanche/connectors/pangolin/exchange/events.sol
 create mode 100644 contracts/avalanche/connectors/pangolin/exchange/helpers.sol
 create mode 100644 contracts/avalanche/connectors/pangolin/exchange/interface.sol
 create mode 100644 contracts/avalanche/connectors/pangolin/exchange/main.sol
 create mode 100644 test/avalanche/pangolin/pangolin.test.ts

diff --git a/contracts/avalanche/connectors/pangolin/exchange/events.sol b/contracts/avalanche/connectors/pangolin/exchange/events.sol
new file mode 100644
index 00000000..2717b0a3
--- /dev/null
+++ b/contracts/avalanche/connectors/pangolin/exchange/events.sol
@@ -0,0 +1,41 @@
+pragma solidity ^0.7.0;
+
+contract Events {
+    event LogDepositLiquidity(
+        address indexed tokenA,
+        address indexed tokenB,
+        uint256 amtA,
+        uint256 amtB,
+        uint256 uniAmount,
+        uint256 getId,
+        uint256 setId
+    );
+
+    event LogWithdrawLiquidity(
+        address indexed tokenA,
+        address indexed tokenB,
+        uint256 amountA,
+        uint256 amountB,
+        uint256 uniAmount,
+        uint256 getId,
+        uint256[] setId
+    );
+    
+    event LogBuy(
+        address indexed buyToken,
+        address indexed sellToken,
+        uint256 buyAmt,
+        uint256 sellAmt,
+        uint256 getId,
+        uint256 setId
+    );
+
+    event LogSell(
+        address indexed buyToken,
+        address indexed sellToken,
+        uint256 buyAmt,
+        uint256 sellAmt,
+        uint256 getId,
+        uint256 setId
+    );
+}
\ No newline at end of file
diff --git a/contracts/avalanche/connectors/pangolin/exchange/helpers.sol b/contracts/avalanche/connectors/pangolin/exchange/helpers.sol
new file mode 100644
index 00000000..3e56046f
--- /dev/null
+++ b/contracts/avalanche/connectors/pangolin/exchange/helpers.sol
@@ -0,0 +1,146 @@
+pragma solidity ^0.7.0;
+
+import { TokenInterface } from "../../../common/interfaces.sol";
+import { DSMath } from "../../../common/math.sol";
+import { Basic } from "../../../common/basic.sol";
+import { IPangolinRouter, IPangolinFactory } from "./interface.sol";
+
+abstract contract Helpers is DSMath, Basic {
+
+    /**
+     * @dev Pangolin Router
+     */
+    IPangolinRouter internal constant router = IPangolinRouter(0xE54Ca86531e17Ef3616d22Ca28b0D458b6C89106);
+
+    function getExpectedBuyAmt(
+        address[] memory paths,
+        uint sellAmt
+    ) internal view returns(uint buyAmt) {
+        uint[] memory amts = router.getAmountsOut(
+            sellAmt,
+            paths
+        );
+        buyAmt = amts[1];
+    }
+
+    function getExpectedSellAmt(
+        address[] memory paths,
+        uint buyAmt
+    ) internal view returns(uint sellAmt) {
+        uint[] memory amts = router.getAmountsIn(
+            buyAmt,
+            paths
+        );
+        sellAmt = amts[0];
+    }
+
+    function checkPair(
+        address[] memory paths
+    ) internal view {
+        address pair = IPangolinFactory(router.factory()).getPair(paths[0], paths[1]);
+        require(pair != address(0), "No-exchange-address");
+    }
+
+    function getPaths(
+        address buyAddr,
+        address sellAddr
+    ) internal pure returns(address[] memory paths) {
+        paths = new address[](2);
+        paths[0] = address(sellAddr);
+        paths[1] = address(buyAddr);
+    }
+
+    function getMinAmount(
+        TokenInterface token,
+        uint amt,
+        uint slippage
+    ) internal view returns(uint minAmt) {
+        uint _amt18 = convertTo18(token.decimals(), amt);
+        minAmt = wmul(_amt18, sub(WAD, slippage));
+        minAmt = convert18ToDec(token.decimals(), minAmt);
+    }
+
+    function _addLiquidity(
+        address tokenA,
+        address tokenB,
+        uint _amt,
+        uint unitAmt,
+        uint slippage
+    ) internal returns (uint _amtA, uint _amtB, uint _liquidity) {
+        (TokenInterface _tokenA, TokenInterface _tokenB) = changeAvaxAddress(tokenA, tokenB);
+
+        _amtA = _amt == uint(-1) ? getTokenBal(TokenInterface(tokenA)) : _amt;
+        _amtB = convert18ToDec(_tokenB.decimals(), wmul(unitAmt, convertTo18(_tokenA.decimals(), _amtA)));
+
+        bool isAvax = address(_tokenA) == wavaxAddr;
+        convertAvaxToWavax(isAvax, _tokenA, _amtA);
+
+        isAvax = address(_tokenB) == wavaxAddr;
+        convertAvaxToWavax(isAvax, _tokenB, _amtB);
+
+        approve(_tokenA, address(router), _amtA);
+        approve(_tokenB, address(router), _amtB);
+
+        uint minAmtA = getMinAmount(_tokenA, _amtA, slippage);
+        uint minAmtB = getMinAmount(_tokenB, _amtB, slippage);
+       (_amtA, _amtB, _liquidity) = router.addLiquidity(
+            address(_tokenA),
+            address(_tokenB),
+            _amtA,
+            _amtB,
+            minAmtA,
+            minAmtB,
+            address(this),
+            block.timestamp + 1
+        );
+    }
+
+    function _removeLiquidity(
+        address tokenA,
+        address tokenB,
+        uint _amt,
+        uint unitAmtA,
+        uint unitAmtB
+    ) internal returns (uint _amtA, uint _amtB, uint _uniAmt) {
+        TokenInterface _tokenA;
+        TokenInterface _tokenB;
+        (_tokenA, _tokenB, _uniAmt) = _getRemoveLiquidityData(
+            tokenA,
+            tokenB,
+            _amt
+        );
+        {
+        uint minAmtA = convert18ToDec(_tokenA.decimals(), wmul(unitAmtA, _uniAmt));
+        uint minAmtB = convert18ToDec(_tokenB.decimals(), wmul(unitAmtB, _uniAmt));
+        (_amtA, _amtB) = router.removeLiquidity(
+            address(_tokenA),
+            address(_tokenB),
+            _uniAmt,
+            minAmtA,
+            minAmtB,
+            address(this),
+            block.timestamp + 1
+        );
+        }
+
+        bool isAvax = address(_tokenA) == wavaxAddr;
+        convertWavaxToAvax(isAvax, _tokenA, _amtA);
+
+        isAvax = address(_tokenB) == wavaxAddr;
+        convertWavaxToAvax(isAvax, _tokenB, _amtB);
+    }
+
+    function _getRemoveLiquidityData(
+        address tokenA,
+        address tokenB,
+        uint _amt
+    ) internal returns (TokenInterface _tokenA, TokenInterface _tokenB, uint _uniAmt) {
+        (_tokenA, _tokenB) = changeAvaxAddress(tokenA, tokenB);
+        address exchangeAddr = IPangolinFactory(router.factory()).getPair(address(_tokenA), address(_tokenB));
+        require(exchangeAddr != address(0), "pair-not-found.");
+
+        TokenInterface pngToken = TokenInterface(exchangeAddr);
+        _uniAmt = _amt == uint(-1) ? pngToken.balanceOf(address(this)) : _amt;
+        approve(pngToken, address(router), _uniAmt);
+    }
+}
\ No newline at end of file
diff --git a/contracts/avalanche/connectors/pangolin/exchange/interface.sol b/contracts/avalanche/connectors/pangolin/exchange/interface.sol
new file mode 100644
index 00000000..ac308099
--- /dev/null
+++ b/contracts/avalanche/connectors/pangolin/exchange/interface.sol
@@ -0,0 +1,164 @@
+pragma solidity >=0.6.2;
+
+interface IPangolinRouter {
+    function factory() external pure returns (address);
+    function WAVAX() external pure returns (address);
+
+    function addLiquidity(
+        address tokenA,
+        address tokenB,
+        uint amountADesired,
+        uint amountBDesired,
+        uint amountAMin,
+        uint amountBMin,
+        address to,
+        uint deadline
+    ) external returns (uint amountA, uint amountB, uint liquidity);
+
+    function addLiquidityAVAX(
+        address token,
+        uint amountTokenDesired,
+        uint amountTokenMin,
+        uint amountAVAXMin,
+        address to,
+        uint deadline
+    ) external payable returns (uint amountToken, uint amountAVAX, uint liquidity);
+
+    function removeLiquidity(
+        address tokenA,
+        address tokenB,
+        uint liquidity,
+        uint amountAMin,
+        uint amountBMin,
+        address to,
+        uint deadline
+    ) external returns (uint amountA, uint amountB);
+
+    function removeLiquidityAVAX(
+        address token,
+        uint liquidity,
+        uint amountTokenMin,
+        uint amountAVAXMin,
+        address to,
+        uint deadline
+    ) external returns (uint amountToken, uint amountAVAX);
+
+    function removeLiquidityWithPermit(
+        address tokenA,
+        address tokenB,
+        uint liquidity,
+        uint amountAMin,
+        uint amountBMin,
+        address to,
+        uint deadline,
+        bool approveMax, uint8 v, bytes32 r, bytes32 s
+    ) external returns (uint amountA, uint amountB);
+
+    function removeLiquidityAVAXWithPermit(
+        address token,
+        uint liquidity,
+        uint amountTokenMin,
+        uint amountAVAXMin,
+        address to,
+        uint deadline,
+        bool approveMax, uint8 v, bytes32 r, bytes32 s
+    ) external returns (uint amountToken, uint amountAVAX);
+
+    function swapExactTokensForTokens(
+        uint amountIn,
+        uint amountOutMin,
+        address[] calldata path,
+        address to,
+        uint deadline
+    ) external returns (uint[] memory amounts);
+
+    function swapTokensForExactTokens(
+        uint amountOut,
+        uint amountInMax,
+        address[] calldata path,
+        address to,
+        uint deadline
+    ) external returns (uint[] memory amounts);
+
+    function swapExactAVAXForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
+        external
+        payable
+        returns (uint[] memory amounts);
+
+    function swapTokensForExactAVAX(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
+        external
+        returns (uint[] memory amounts);
+
+    function swapExactTokensForAVAX(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
+        external
+        returns (uint[] memory amounts);
+
+    function swapAVAXForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
+        external
+        payable
+        returns (uint[] memory amounts);
+
+    function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
+    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
+    function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
+    function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
+    function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
+
+    function removeLiquidityAVAXSupportingFeeOnTransferTokens(
+        address token,
+        uint liquidity,
+        uint amountTokenMin,
+        uint amountAVAXMin,
+        address to,
+        uint deadline
+    ) external returns (uint amountAVAX);
+
+    function removeLiquidityAVAXWithPermitSupportingFeeOnTransferTokens(
+        address token,
+        uint liquidity,
+        uint amountTokenMin,
+        uint amountAVAXMin,
+        address to,
+        uint deadline,
+        bool approveMax, uint8 v, bytes32 r, bytes32 s
+    ) external returns (uint amountAVAX);
+
+    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
+        uint amountIn,
+        uint amountOutMin,
+        address[] calldata path,
+        address to,
+        uint deadline
+    ) external;
+
+    function swapExactAVAXForTokensSupportingFeeOnTransferTokens(
+        uint amountOutMin,
+        address[] calldata path,
+        address to,
+        uint deadline
+    ) external payable;
+
+    function swapExactTokensForAVAXSupportingFeeOnTransferTokens(
+        uint amountIn,
+        uint amountOutMin,
+        address[] calldata path,
+        address to,
+        uint deadline
+    ) external;
+}
+
+interface IPangolinFactory {
+    event PairCreated(address indexed token0, address indexed token1, address pair, uint);
+
+    function getPair(address tokenA, address tokenB) external view returns (address pair);
+    function allPairs(uint) external view returns (address pair);
+    function allPairsLength() external view returns (uint);
+
+    function feeTo() external view returns (address);
+    function feeToSetter() external view returns (address);
+
+    function createPair(address tokenA, address tokenB) external returns (address pair);
+
+    function setFeeTo(address) external;
+    function setFeeToSetter(address) external;
+}
\ No newline at end of file
diff --git a/contracts/avalanche/connectors/pangolin/exchange/main.sol b/contracts/avalanche/connectors/pangolin/exchange/main.sol
new file mode 100644
index 00000000..0f436906
--- /dev/null
+++ b/contracts/avalanche/connectors/pangolin/exchange/main.sol
@@ -0,0 +1,196 @@
+pragma solidity ^0.7.0;
+
+/**
+ * @title Pangolin.
+ * @dev Decentralized Exchange.
+ */
+
+import { TokenInterface } from "../../../common/interfaces.sol";
+import { Helpers } from "./helpers.sol";
+import { Events } from "./events.sol";
+
+abstract contract PangolinResolver is Helpers, Events {
+    /**
+     * @dev Deposit Liquidity.
+     * @notice Deposit Liquidity to a Pangolin pool.
+     * @param tokenA The address of token A.(For AVAX: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
+     * @param tokenB The address of token B.(For AVAX: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
+     * @param amtA The amount of A tokens to deposit.
+     * @param unitAmt The unit amount of of amtB/amtA with slippage.
+     * @param slippage Slippage amount.
+     * @param getId ID to retrieve amtA.
+     * @param setId ID stores the amount of pools tokens received.
+    */
+    function deposit(
+        address tokenA,
+        address tokenB,
+        uint256 amtA,
+        uint256 unitAmt,
+        uint256 slippage,
+        uint256 getId,
+        uint256 setId
+    ) external payable returns (string memory _eventName, bytes memory _eventParam) {
+        uint _amt = getUint(getId, amtA);
+
+        (uint _amtA, uint _amtB, uint _uniAmt) = _addLiquidity(
+                                            tokenA,
+                                            tokenB,
+                                            _amt,
+                                            unitAmt,
+                                            slippage
+                                        );
+        setUint(setId, _uniAmt);
+        
+        _eventName = "LogDepositLiquidity(address,address,uint256,uint256,uint256,uint256,uint256)";
+        _eventParam = abi.encode(tokenA, tokenB, _amtA, _amtB, _uniAmt, getId, setId);
+    }
+
+    /**
+     * @dev Withdraw Liquidity.
+     * @notice Withdraw Liquidity from a Pangolin pool.
+     * @param tokenA The address of token A.(For AVAX: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
+     * @param tokenB The address of token B.(For AVAX: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
+     * @param uniAmt The amount of pool tokens to withdraw.
+     * @param unitAmtA The unit amount of amtA/uniAmt with slippage.
+     * @param unitAmtB The unit amount of amtB/uniAmt with slippage.
+     * @param getId ID to retrieve uniAmt.
+     * @param setIds Array of IDs to store the amount tokens received.
+    */
+    function withdraw(
+        address tokenA,
+        address tokenB,
+        uint256 uniAmt,
+        uint256 unitAmtA,
+        uint256 unitAmtB,
+        uint256 getId,
+        uint256[] calldata setIds
+    ) external payable returns (string memory _eventName, bytes memory _eventParam) {
+        uint _amt = getUint(getId, uniAmt);
+
+        (uint _amtA, uint _amtB, uint _uniAmt) = _removeLiquidity(
+            tokenA,
+            tokenB,
+            _amt,
+            unitAmtA,
+            unitAmtB
+        );
+
+        setUint(setIds[0], _amtA);
+        setUint(setIds[1], _amtB);
+        
+        _eventName = "LogWithdrawLiquidity(address,address,uint256,uint256,uint256,uint256,uint256[])";
+        _eventParam = abi.encode(tokenA, tokenB, _amtA, _amtB, _uniAmt, getId, setIds);
+    }
+
+    /**
+     * @dev Buy AVAX/ERC20_Token.
+     * @notice Buy a token using a Pangolin
+     * @param buyAddr The address of the token to buy.(For AVAX: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
+     * @param sellAddr The address of the token to sell.(For AVAX: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
+     * @param buyAmt The amount of tokens to buy.
+     * @param unitAmt The unit amount of sellAmt/buyAmt with slippage.
+     * @param getId ID to retrieve buyAmt.
+     * @param setId ID to store the amount of tokens sold.
+    */
+    function buy(
+        address buyAddr,
+        address sellAddr,
+        uint256 buyAmt,
+        uint256 unitAmt,
+        uint256 getId,
+        uint256 setId
+    ) external payable returns (string memory _eventName, bytes memory _eventParam) {
+        uint _buyAmt = getUint(getId, buyAmt);
+        (TokenInterface _buyAddr, TokenInterface _sellAddr) = changeAvaxAddress(buyAddr, sellAddr);
+        address[] memory paths = getPaths(address(_buyAddr), address(_sellAddr));
+
+        uint _slippageAmt = convert18ToDec(_sellAddr.decimals(),
+            wmul(unitAmt, convertTo18(_buyAddr.decimals(), _buyAmt))
+        );
+
+        checkPair(paths);
+        uint _expectedAmt = getExpectedSellAmt(paths, _buyAmt);
+        require(_slippageAmt >= _expectedAmt, "Too much slippage");
+
+        bool isAvax = address(_sellAddr) == wavaxAddr;
+        convertAvaxToWavax(isAvax, _sellAddr, _expectedAmt);
+        approve(_sellAddr, address(router), _expectedAmt);
+
+        uint _sellAmt = router.swapTokensForExactTokens(
+            _buyAmt,
+            _expectedAmt,
+            paths,
+            address(this),
+            block.timestamp + 1
+        )[0];
+
+        isAvax = address(_buyAddr) == wavaxAddr;
+        convertWavaxToAvax(isAvax, _buyAddr, _buyAmt);
+
+        setUint(setId, _sellAmt);
+
+        _eventName = "LogBuy(address,address,uint256,uint256,uint256,uint256)";
+        _eventParam = abi.encode(buyAddr, sellAddr, _buyAmt, _sellAmt, getId, setId);
+    }
+
+    /**
+     * @dev Sell AVAX/ERC20_Token.
+     * @notice Sell a token using a Pangolin
+     * @param buyAddr The address of the token to buy.(For AVAX: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
+     * @param sellAddr The address of the token to sell.(For AVAX: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
+     * @param sellAmt The amount of the token to sell.
+     * @param unitAmt The unit amount of buyAmt/sellAmt with slippage.
+     * @param getId ID to retrieve sellAmt.
+     * @param setId ID stores the amount of token brought.
+    */
+    function sell(
+        address buyAddr,
+        address sellAddr,
+        uint256 sellAmt,
+        uint256 unitAmt,
+        uint256 getId,
+        uint256 setId
+    ) external payable returns (string memory _eventName, bytes memory _eventParam) {
+        uint _sellAmt = getUint(getId, sellAmt);
+        (TokenInterface _buyAddr, TokenInterface _sellAddr) = changeAvaxAddress(buyAddr, sellAddr);
+        address[] memory paths = getPaths(address(_buyAddr), address(_sellAddr));
+
+        if (_sellAmt == uint(-1)) {
+            _sellAmt = sellAddr == avaxAddr ?
+                address(this).balance :
+                _sellAddr.balanceOf(address(this));
+        }
+
+        uint _slippageAmt = convert18ToDec(_buyAddr.decimals(),
+            wmul(unitAmt, convertTo18(_sellAddr.decimals(), _sellAmt))
+        );
+
+        checkPair(paths);
+        uint _expectedAmt = getExpectedBuyAmt(paths, _sellAmt);
+        require(_slippageAmt <= _expectedAmt, "Too much slippage");
+
+        bool isAvax = address(_sellAddr) == wavaxAddr;
+        convertAvaxToWavax(isAvax, _sellAddr, _sellAmt);
+        approve(_sellAddr, address(router), _sellAmt);
+
+        uint _buyAmt = router.swapExactTokensForTokens(
+            _sellAmt,
+            _expectedAmt,
+            paths,
+            address(this),
+            block.timestamp + 1
+        )[1];
+
+        isAvax = address(_buyAddr) == wavaxAddr;
+        convertWavaxToAvax(isAvax, _buyAddr, _buyAmt);
+
+        setUint(setId, _buyAmt);
+
+        _eventName = "LogSell(address,address,uint256,uint256,uint256,uint256)";
+        _eventParam = abi.encode(buyAddr, sellAddr, _buyAmt, _sellAmt, getId, setId);
+    }
+}
+
+contract ConnectV2PngAvalanche is PangolinResolver {
+    string public constant name = "Pangolin-v1";
+}
diff --git a/test/avalanche/pangolin/pangolin.test.ts b/test/avalanche/pangolin/pangolin.test.ts
new file mode 100644
index 00000000..97f80eab
--- /dev/null
+++ b/test/avalanche/pangolin/pangolin.test.ts
@@ -0,0 +1,283 @@
+import { expect } from "chai";
+import hre from "hardhat";
+
+const { web3, deployments, waffle, ethers } = hre;
+const { provider, deployContract } = waffle;
+
+import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector";
+import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2";
+import { encodeSpells } from "../../../scripts/tests/encodeSpells";
+import { getMasterSigner } from "../../../scripts/tests/getMasterSigner";
+import { addLiquidity } from "../../../scripts/tests/addLiquidity";
+import { addresses } from "../../../scripts/tests/avalanche/addresses";
+import { abis } from "../../../scripts/constant/abis";
+import type { Signer, Contract } from "ethers";
+
+import { ConnectV2PngAvalanche__factory } from "../../../typechain";
+
+const PNG_ADDRESS  = "0x60781C2586D68229fde47564546784ab3fACA982";
+const WAVAX_ADDRESS = "0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7";
+const PNG_AVAX_LP_ADDRESS = "0xd7538cABBf8605BdE1f4901B47B8D42c61DE0367";
+
+describe("Pangolin DEX - Avalanche", function () {
+    const pangolinConnectorName = "PANGOLIN-TEST-A"
+    
+    let dsaWallet0: any;
+    let masterSigner: any;
+    let instaConnectorsV2: any;
+    let pangolinConnector: any;
+    
+    const wallets = provider.getWallets()
+    const [wallet0, wallet1, wallet2, wallet3] = wallets
+    before(async () => {
+        await hre.network.provider.request({
+            method: "hardhat_reset",
+            params: [
+                {
+                    forking: {
+                        jsonRpcUrl: `https://api.avax.network/ext/bc/C/rpc`,
+                        blockNumber: 8197390
+                    },
+                },
+            ],
+        });
+
+        masterSigner = await getMasterSigner();
+        instaConnectorsV2 = await ethers.getContractAt(
+            abis.core.connectorsV2,
+            addresses.core.connectorsV2
+        );
+
+        // Deploy and enable Pangolin Connector
+        pangolinConnector = await deployAndEnableConnector({
+            connectorName: pangolinConnectorName,
+            contractArtifact: ConnectV2PngAvalanche__factory,
+            signer: masterSigner,
+            connectors: instaConnectorsV2
+        });
+        console.log("Pangolin Connector address: "+ pangolinConnector.address);
+    })
+
+    it("Should have contracts deployed.", async function () {
+        expect(!!instaConnectorsV2.address).to.be.true;
+        expect(!!pangolinConnector.address).to.be.true;
+        expect(!!masterSigner.address).to.be.true;
+      });
+    
+    describe("DSA wallet setup", function () {
+        it("Should build DSA v2", async function () {
+            dsaWallet0 = await buildDSAv2(wallet0.address)
+            expect(!!dsaWallet0.address).to.be.true;
+        });
+
+        it("Deposit 10 AVAX into DSA wallet", async function () {
+            await wallet0.sendTransaction({
+                to: dsaWallet0.address,
+                value: ethers.utils.parseEther("10")
+            });
+            expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10"));
+        });
+    });
+
+    describe("Main - PANGOLIN PNG/AVAX Liquidity Test", function () {
+
+        it("Should use pangolin to swap AVAX for PNG, and deposit to PNG/AVAX LP", async function () {
+            const amount = ethers.utils.parseEther("100"); // 100 PNG
+            const int_slippage = 0.03
+            const slippage = ethers.utils.parseEther(int_slippage.toString());
+            const setId = "83528353";
+    
+            const PangolinRouterABI = [
+                "function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)"
+            ];
+    
+            // Get amount of AVAX for 100 POOL from Pangolin
+            const PangolinRouter = await ethers.getContractAt(
+                PangolinRouterABI, 
+                "0xE54Ca86531e17Ef3616d22Ca28b0D458b6C89106"
+            );
+            const amounts = await PangolinRouter.getAmountsOut(
+                amount, 
+                [
+                    PNG_ADDRESS, 
+                    WAVAX_ADDRESS
+                ]
+            );
+
+            const amtA = amounts[0];
+            const amtB = amounts[1];
+            const unitAmt = (amtB * (1 + int_slippage)) / amtA;
+            const unitAmount = ethers.utils.parseEther(unitAmt.toString());
+
+            const spells = [
+                {
+                    connector: pangolinConnectorName,
+                    method: "buy",
+                    args: [
+                        PNG_ADDRESS, 
+                        "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", 
+                        amount, 
+                        unitAmount, 
+                        0, 
+                        setId
+                    ]
+                },
+                {
+                    connector: pangolinConnectorName,
+                    method: "deposit",
+                    args: [
+                        PNG_ADDRESS, 
+                        "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", 
+                        amount, 
+                        unitAmount, 
+                        slippage, 
+                        0, 
+                        setId
+                    ]
+                },
+            ];
+    
+            // Before Spell
+            let avaxBalance = await ethers.provider.getBalance(dsaWallet0.address);
+            expect(avaxBalance, `AVAX Balance equals 10`).to.be.eq(ethers.utils.parseEther("10"));
+    
+            let pngToken = await ethers.getContractAt(abis.basic.erc20, PNG_ADDRESS);
+            const pngBalance = await pngToken.balanceOf(dsaWallet0.address);
+            expect(pngBalance, `PNG Token greater than 0`).to.be.eq(0);
+    
+            let pangolinLPToken = await ethers.getContractAt(
+                abis.basic.erc20, 
+                PNG_AVAX_LP_ADDRESS
+            );
+            const pangolinPoolAVAXBalance = await pangolinLPToken.balanceOf(dsaWallet0.address);
+            expect(pangolinPoolAVAXBalance, `Pangolin PNG/AVAX LP equals 0`).to.be.eq(0);
+    
+            // Run spell transaction
+            const tx = await dsaWallet0.connect(wallet0).cast(
+                ...encodeSpells(spells), wallet1.address
+            );
+            const receipt = await tx.wait();
+    
+            // After spell
+            avaxBalance = await ethers.provider.getBalance(dsaWallet0.address);
+            expect(avaxBalance, `AVAX Balance less than 10`).to.be.lt(ethers.utils.parseEther("10"));
+    
+            const pngBalanceAfter = await pngToken.balanceOf(dsaWallet0.address)
+            expect(pngBalanceAfter, `PNG Token to be same after spell`).to.be.eq(pngBalance);
+    
+            const pangolinPoolAVAXBalanceAfter = await pangolinLPToken.balanceOf(dsaWallet0.address);
+            expect(
+                pangolinPoolAVAXBalanceAfter, 
+                `Pangolin PNG/AVAX LP greater than 0`
+            ).to.be.gt(0);
+        });
+
+        it("Should use pangolin to withdraw to PNG/AVAX LP, and swap PNG for AVAX", async function () {
+            const amount = ethers.utils.parseEther("100"); // 100 PNG
+            const int_slippage = 0.03
+    
+            // Before Spell
+            let avaxBalance = await ethers.provider.getBalance(dsaWallet0.address);
+            let pngToken = await ethers.getContractAt(abis.basic.erc20, PNG_ADDRESS);
+            let pangolinLPToken = await ethers.getContractAt(
+                abis.basic.erc20, 
+                PNG_AVAX_LP_ADDRESS
+            );
+
+            const pngBalance = await pngToken.balanceOf(dsaWallet0.address)
+            expect(pngBalance, `PNG Token balance equal to 0`).to.be.eq(0);
+    
+            const pangolinPoolAVAXBalance = await pangolinLPToken.balanceOf(dsaWallet0.address);
+            expect(pangolinPoolAVAXBalance, `Pangolin PNG/AVAX LP greater than 0`).to.be.gt(0);
+    
+            const PangolinRouterABI = [
+                "function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)"
+            ];
+    
+            // Get amount of avax for 100 PNG from Pangolin
+            const PangolinRouter= await ethers.getContractAt(
+                PangolinRouterABI, 
+                "0xE54Ca86531e17Ef3616d22Ca28b0D458b6C89106"
+            );
+            const amounts = await PangolinRouter.getAmountsOut(
+                amount, 
+                [
+                    PNG_ADDRESS,
+                    WAVAX_ADDRESS
+                ]
+            );
+            const amtA = amounts[0];
+            const amtB = amounts[1];
+            const unitAmtA = ethers.utils.parseEther(
+                (amtA * (1 - int_slippage) / pangolinPoolAVAXBalance).toString()
+            );
+            const unitAmtB = ethers.utils.parseEther(
+                (amtB * (1 - int_slippage) / pangolinPoolAVAXBalance).toString()
+            );
+
+            let spells = [
+                {
+                    connector: pangolinConnectorName,
+                    method: "withdraw",
+                    args: [
+                        PNG_ADDRESS, 
+                        "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", 
+                        pangolinPoolAVAXBalance, 
+                        unitAmtA, 
+                        unitAmtB, 
+                        0, 
+                        [
+                            0,
+                            0
+                        ]
+                    ]
+                },
+            ];
+
+            // Run spell transaction (withdraw token of pool)
+            const tx = await dsaWallet0.connect(wallet0).cast(
+                ...encodeSpells(spells), 
+                wallet1.address
+            );
+            const receipt = await tx.wait();
+    
+            // After spell
+            const pangolinPoolAVAXBalanceAfter = await pangolinLPToken.balanceOf(
+                dsaWallet0.address
+            );
+            expect(pangolinPoolAVAXBalanceAfter, `Pangolin PNG/AVAX LP equal 0`).to.be.eq(0);
+
+            let pngBalanceAfter = await pngToken.balanceOf(dsaWallet0.address);
+            expect(pngBalanceAfter, `PNG Token balance greater than`).to.be.gt(0);
+            const unitAmt = amount.div(pngBalanceAfter);
+
+            spells = [
+                {
+                    connector: pangolinConnectorName,
+                    method: "sell",
+                    args: [
+                        "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", 
+                        PNG_ADDRESS, 
+                        pngBalanceAfter, 
+                        unitAmt, 
+                        0, 
+                        0
+                    ]
+                },
+            ];
+
+            // Run spell transaction (withdraw token of pool)
+            const tx2 = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address);
+            const receipt2 = await tx2.wait();
+    
+            let avaxBalanceAfter = await ethers.provider.getBalance(dsaWallet0.address);
+            expect(
+                avaxBalanceAfter, 
+                `AVAX Balance After greater than AVAX Balance Before`
+            ).to.be.gt(avaxBalance);
+    
+            pngBalanceAfter = await pngToken.balanceOf(dsaWallet0.address);
+            expect(pngBalanceAfter, `PNG Token balance equal 0`).to.be.eq(0);
+        });
+      })
+});

From 7884bb31e0c77a17c5cb79b9c0eda0fd854a27a3 Mon Sep 17 00:00:00 2001
From: Pedro <pedroantonioppms@gmail.com>
Date: Mon, 20 Dec 2021 17:50:05 -0400
Subject: [PATCH 2/2] Add Pangolin Stake Connector and Tests

Resolve errors

Resolve review issues

Fix comment

Co-authored-by: 0xPradyuman <63545809+pradyuman-verma@users.noreply.github.com>

Added NatSpec to Staking Connector

Fix NatSpecs
---
 .../connectors/pangolin/exchange/main.sol     |   2 +-
 .../connectors/pangolin/staking/events.sol    |  67 ++
 .../connectors/pangolin/staking/helpers.sol   | 160 ++++
 .../connectors/pangolin/staking/interface.sol |  49 ++
 .../connectors/pangolin/staking/main.sol      | 190 ++++
 ...olin.test.ts => pangolin_exchange.test.ts} |  21 +-
 .../avalanche/pangolin/pangolin_stake.test.ts | 821 ++++++++++++++++++
 7 files changed, 1298 insertions(+), 12 deletions(-)
 create mode 100644 contracts/avalanche/connectors/pangolin/staking/events.sol
 create mode 100644 contracts/avalanche/connectors/pangolin/staking/helpers.sol
 create mode 100644 contracts/avalanche/connectors/pangolin/staking/interface.sol
 create mode 100644 contracts/avalanche/connectors/pangolin/staking/main.sol
 rename test/avalanche/pangolin/{pangolin.test.ts => pangolin_exchange.test.ts} (95%)
 create mode 100644 test/avalanche/pangolin/pangolin_stake.test.ts

diff --git a/contracts/avalanche/connectors/pangolin/exchange/main.sol b/contracts/avalanche/connectors/pangolin/exchange/main.sol
index 0f436906..c4e866d9 100644
--- a/contracts/avalanche/connectors/pangolin/exchange/main.sol
+++ b/contracts/avalanche/connectors/pangolin/exchange/main.sol
@@ -16,7 +16,7 @@ abstract contract PangolinResolver is Helpers, Events {
      * @param tokenA The address of token A.(For AVAX: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
      * @param tokenB The address of token B.(For AVAX: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
      * @param amtA The amount of A tokens to deposit.
-     * @param unitAmt The unit amount of of amtB/amtA with slippage.
+     * @param unitAmt The unit amount of amtB/amtA with slippage.
      * @param slippage Slippage amount.
      * @param getId ID to retrieve amtA.
      * @param setId ID stores the amount of pools tokens received.
diff --git a/contracts/avalanche/connectors/pangolin/staking/events.sol b/contracts/avalanche/connectors/pangolin/staking/events.sol
new file mode 100644
index 00000000..84c61559
--- /dev/null
+++ b/contracts/avalanche/connectors/pangolin/staking/events.sol
@@ -0,0 +1,67 @@
+pragma solidity ^0.7.0;
+
+contract Events {
+    event LogDepositLpStake(
+        address indexed lptoken,
+        uint256 indexed pid,
+        uint256 stakedAmount,
+        uint256 getId,
+        uint256 setId
+    );
+ 
+    event LogWithdrawLpStake(
+        address indexed lptoken,
+        uint256 indexed pid,
+        uint256 withdrawAmount,
+        uint256 getId,
+        uint256 setId
+    );
+
+    event LogWithdrawLpAndClaim(
+        address indexed lptoken,
+        uint256 indexed pid,
+        uint256 withdrawAmount,
+        uint256 rewardAmount,
+        uint256 getId,
+        uint256 setId
+    );
+
+    event LogClaimLpReward(
+        address indexed lptoken,
+        uint256 indexed pid,
+        uint256 rewardAmount
+    );
+
+    event LogEmergencyWithdrawLpStake(
+        address indexed lptoken,
+        uint256 indexed pid,
+        uint256 withdrawAmount
+    );
+
+    event LogDepositPNGStake(
+        address indexed stakingContract,
+        uint256 stakedAmount,
+        uint256 getId,
+        uint256 setId
+    );
+
+    event LogWithdrawPNGStake(
+        address indexed stakingContract,
+        uint256 withdrawAmount,
+        uint256 getId,
+        uint256 setId
+    );
+
+    event LogExitPNGStake(
+        address indexed stakingContract,
+        uint256 exitAmount,
+        uint256 rewardAmount,
+        address indexed rewardToken
+    );
+
+    event LogClaimPNGStakeReward(
+        address indexed stakingContract,
+        uint256 rewardAmount,
+        address indexed rewardToken
+    );
+}
\ No newline at end of file
diff --git a/contracts/avalanche/connectors/pangolin/staking/helpers.sol b/contracts/avalanche/connectors/pangolin/staking/helpers.sol
new file mode 100644
index 00000000..34aa6e91
--- /dev/null
+++ b/contracts/avalanche/connectors/pangolin/staking/helpers.sol
@@ -0,0 +1,160 @@
+pragma solidity ^0.7.0;
+pragma abicoder v2;
+
+import { TokenInterface } from "../../../common/interfaces.sol";
+import { DSMath } from "../../../common/math.sol";
+import { Basic } from "../../../common/basic.sol";
+import { IERC20, IMiniChefV2, IStakingRewards } from "./interface.sol";
+
+abstract contract Helpers is DSMath, Basic {
+
+    /**
+     * @dev Pangolin MiniChefV2
+     */
+    IMiniChefV2 internal constant minichefv2 = IMiniChefV2(0x1f806f7C8dED893fd3caE279191ad7Aa3798E928);
+
+    /**
+     * @dev Pangolin Token
+     */
+    IERC20 internal constant PNG = IERC20(0x60781C2586D68229fde47564546784ab3fACA982);
+
+    // LP Staking, use minichefv2 to staking lp tokens and earn png
+    function _depositLPStake(
+        uint pid,
+        uint amount
+    ) internal returns (address lpTokenAddr) {
+        require(pid < minichefv2.poolLength(), "Invalid pid!");
+        IERC20 lptoken = minichefv2.lpToken(pid);
+
+        require(amount > 0, "Invalid amount, amount cannot be 0");
+        require(lptoken.balanceOf(address(this)) > 0, "Invalid LP token balance");
+        require(lptoken.balanceOf(address(this)) >= amount, "Invalid amount, amount greater than balance of LP token");
+
+        approve(
+            lptoken, 
+            address(minichefv2), 
+            amount
+        );
+
+        minichefv2.deposit(pid, amount, address(this));
+        lpTokenAddr = address(lptoken);
+    }
+
+    function _withdraw_LP_Stake(
+        uint pid,
+        uint amount
+    ) internal returns (address lpTokenAddr) {
+        require(pid < minichefv2.poolLength(), "Invalid pid!");
+        
+        IMiniChefV2.UserInfo memory userinfo = minichefv2.userInfo(pid, address(this));
+
+        require(userinfo.amount >= amount, "Invalid amount, amount greater than balance of staking");
+        require(amount > 0, "Invalid amount, amount cannot be 0");
+
+        minichefv2.withdraw(pid, amount, address(this));
+
+        IERC20 lptoken = minichefv2.lpToken(pid);
+        lpTokenAddr = address(lptoken);
+    }
+
+    function _withdraw_and_getRewards_LP_Stake(
+        uint pid,
+        uint amount
+    ) internal returns (uint256 rewardAmount, address lpTokenAddr) {
+        require(pid < minichefv2.poolLength(), "Invalid pid!");
+
+        IMiniChefV2.UserInfo memory userinfo = minichefv2.userInfo(pid, address(this));
+
+        require(userinfo.amount >=  amount, "Invalid amount, amount greater than balance of staking");
+        require(amount > 0, "Invalid amount, amount cannot be 0");
+
+        rewardAmount = minichefv2.pendingReward(pid, address(this));
+
+        minichefv2.withdrawAndHarvest(pid, amount, address(this));
+
+        IERC20 lptoken = minichefv2.lpToken(pid);
+        lpTokenAddr = address(lptoken);
+    }
+
+    function _getLPStakeReward(
+        uint pid
+    ) internal returns (uint256 rewardAmount, address lpTokenAddr) {
+        require(pid < minichefv2.poolLength(), "Invalid pid!");
+
+        rewardAmount = minichefv2.pendingReward(pid, address(this));
+
+        require(rewardAmount > 0, "No rewards to claim");
+
+        minichefv2.harvest(pid, address(this));
+
+        IERC20 lptoken = minichefv2.lpToken(pid);
+        lpTokenAddr = address(lptoken);
+    }
+
+    function _emergencyWithdraw_LP_Stake(
+        uint pid
+    ) internal returns (uint256 lpAmount, address lpTokenAddr) {
+        require(pid < minichefv2.poolLength(), "Invalid pid!");
+
+        IMiniChefV2.UserInfo memory userinfo = minichefv2.userInfo(pid, address(this));
+        lpAmount = userinfo.amount;
+
+        minichefv2.emergencyWithdraw(pid, address(this));
+        IERC20 lptoken = minichefv2.lpToken(pid);
+        lpTokenAddr = address(lptoken);
+    }
+
+    // PNG Staking (Stake PNG, earn another token)
+    function _depositPNGStake(
+        address stakingContract_addr,
+        uint amount
+    ) internal {
+        IStakingRewards stakingContract = IStakingRewards(stakingContract_addr);
+
+        require(amount > 0, "Invalid amount, amount cannot be 0");
+        require(PNG.balanceOf(address(this)) > 0, "Invalid PNG balance");
+        require(PNG.balanceOf(address(this)) >=  amount, "Invalid amount, amount greater than balance of PNG");
+
+        approve(PNG, stakingContract_addr, amount);
+
+        stakingContract.stake(amount);
+    }
+
+    function _withdrawPNGStake(
+        address stakingContract_addr,
+        uint amount
+    ) internal {
+        IStakingRewards stakingContract = IStakingRewards(stakingContract_addr);
+
+        require(stakingContract.balanceOf(address(this)) >=  amount, "Invalid amount, amount greater than balance of staking");
+        require(amount > 0, "Invalid amount, amount cannot be 0");
+
+        stakingContract.withdraw(amount);
+    }
+
+    function _exitPNGStake(
+        address stakingContract_addr
+    ) internal returns (uint256 exitAmount, uint256 rewardAmount, address rewardToken){
+        IStakingRewards stakingContract = IStakingRewards(stakingContract_addr);
+
+        exitAmount = stakingContract.balanceOf(address(this));
+        rewardAmount = stakingContract.rewards(address(this));
+
+        require(exitAmount > 0, "No balance to exit");
+
+        stakingContract.exit();
+    }
+
+    function _claimPNGStakeReward(
+        address stakingContract_addr
+    ) internal returns (uint256 rewardAmount, address rewardToken) {
+        IStakingRewards stakingContract = IStakingRewards(stakingContract_addr);
+
+        rewardAmount = stakingContract.rewards(address(this));
+        rewardToken = stakingContract.rewardsToken();
+
+        require(rewardAmount > 0, "No rewards to claim");
+
+        stakingContract.getReward();
+    }
+}
diff --git a/contracts/avalanche/connectors/pangolin/staking/interface.sol b/contracts/avalanche/connectors/pangolin/staking/interface.sol
new file mode 100644
index 00000000..8f1d8bb3
--- /dev/null
+++ b/contracts/avalanche/connectors/pangolin/staking/interface.sol
@@ -0,0 +1,49 @@
+pragma solidity >=0.6.2;
+pragma abicoder v2;
+
+import { TokenInterface } from "../../../common/interfaces.sol";
+
+interface IERC20 is TokenInterface{
+
+    // EIP 2612
+    function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external;
+}
+
+interface IStakingRewards {
+    // Storage
+    function rewards(address account) view external returns (uint256);
+
+    // View
+    function balanceOf(address account) external view returns (uint256);
+    function rewardsToken() external view returns (address);
+
+    // Mutative
+    function exit() external;
+    function getReward() external;
+    function stake(uint256 amount) external;
+    function withdraw(uint256 amount) external;
+}
+
+interface IMiniChefV2 {
+    struct UserInfo {
+        uint256 amount;
+        int256 rewardDebt;
+    }
+
+    // Storage
+    function addedTokens(address token) external returns (bool);
+    function lpToken(uint256 _pid) external view returns (IERC20);
+    function userInfo(uint256 _pid, address _user) external view returns (UserInfo memory);
+
+    // View
+    function pendingReward(uint256 _pid, address _user) external view returns (uint256);
+    function poolLength() external view returns (uint256);
+
+    // Mutative
+    function deposit(uint256 pid, uint256 amount, address to) external;
+    function depositWithPermit(uint256 pid, uint256 amount, address to, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
+    function withdraw(uint256 pid, uint256 amount, address to) external;
+    function harvest(uint256 pid, address to) external;
+    function withdrawAndHarvest(uint256 pid, uint256 amount, address to) external;
+    function emergencyWithdraw(uint256 pid, address to) external;
+}
\ No newline at end of file
diff --git a/contracts/avalanche/connectors/pangolin/staking/main.sol b/contracts/avalanche/connectors/pangolin/staking/main.sol
new file mode 100644
index 00000000..117d5909
--- /dev/null
+++ b/contracts/avalanche/connectors/pangolin/staking/main.sol
@@ -0,0 +1,190 @@
+pragma solidity ^0.7.0;
+
+/**
+ * @title Pangolin.
+ * @dev Decentralized Exchange.
+ */
+
+import { TokenInterface } from "../../../common/interfaces.sol";
+import { Helpers } from "./helpers.sol";
+import { Events } from "./events.sol";
+
+abstract contract PangolinStakeResolver is Helpers, Events {
+
+    // LP Staking
+    /**
+    * @notice Deposit LP token in MiniChefV2
+    * @dev Use the Pangolin Stake resolver to get the pid
+    * @param pid The index of the LP token in MiniChefV2.
+    * @param amount The amount of the LP token to deposit.
+    * @param getId ID to retrieve sellAmt.
+    * @param setId ID stores the amount of token brought.
+    */
+    function depositLpStake(
+        uint pid,
+        uint amount,
+        uint256 getId,
+        uint256 setId
+    ) external returns (string memory _eventName, bytes memory _eventParam) {
+        uint _amt = getUint(getId, amount);
+
+        address lpTokenAddr = _depositLPStake(pid, _amt);
+
+        setUint(setId, _amt);
+        _eventName = "LogDepositLpStake(address,uint256,uint256,uint256,uint256)";
+        _eventParam = abi.encode(lpTokenAddr, pid, _amt, getId, setId);
+    }
+
+    /**
+    * @notice Withdraw LP token from MiniChefV2
+    * @dev Use the Pangolin Stake resolver to get the pid
+    * @param pid The index of the LP token in MiniChefV2.
+    * @param amount The amount of the LP token to withdraw.
+    * @param getId ID to retrieve sellAmt.
+    * @param setId ID stores the amount of token brought.
+    */
+    function withdrawLpStake(
+        uint pid,
+        uint amount,
+        uint256 getId,
+        uint256 setId
+    ) external returns (string memory _eventName, bytes memory _eventParam) {
+        uint _amt = getUint(getId, amount);
+
+        address lpTokenAddr = _withdraw_LP_Stake(pid, _amt);
+
+        setUint(setId, _amt);
+
+        _eventName = "LogWithdrawLpStake(address,uint256,uint256,uint256,uint256)";
+        _eventParam = abi.encode(lpTokenAddr, pid, _amt, getId, setId);
+    }
+
+    /**
+    * @notice Withdraw LP token staked and claim rewards from MiniChefV2
+    * @dev Use the Pangolin Stake resolver to get the pid
+    * @param pid The index of the LP token in MiniChefV2.
+    * @param amount The amount of the LP token to withdraw.
+    * @param getId ID to retrieve sellAmt.
+    * @param setId ID stores the amount of token brought.
+    */
+    function withdrawAndClaimLpRewards(
+        uint pid,
+        uint amount,
+        uint256 getId,
+        uint256 setId
+    ) external returns (string memory _eventName, bytes memory _eventParam) {
+        uint _amt = getUint(getId, amount);
+
+        (uint256 rewardAmount, address lpTokenAddr) = _withdraw_and_getRewards_LP_Stake(pid, _amt);
+
+        setUint(setId, _amt);
+
+        _eventName = "LogWithdrawLpAndClaim(address,uint256,uint256,uint256,uint256,uint256)";
+        _eventParam = abi.encode(lpTokenAddr, pid, _amt, rewardAmount, getId, setId);
+    }
+
+    /**
+    * @notice Claim rewards from MiniChefV2
+    * @dev Use the Pangolin Stake resolver to get the pid
+    * @param pid The index of the LP token in MiniChefV2.
+    */
+    function claimLpRewards(
+        uint pid
+    ) external returns (string memory _eventName, bytes memory _eventParam) {
+        (uint256 rewardAmount, address lpTokenAddr) = _getLPStakeReward(pid);
+
+        _eventName = "LogClaimLpReward(address,uint256,uint256)";
+        _eventParam = abi.encode(lpTokenAddr, pid, rewardAmount);
+    }
+
+    /**
+    * @notice Emergency withdraw all LP token staked from MiniChefV2
+    * @dev Use the Pangolin Stake resolver to get the pid
+    * @param pid The index of the LP token in MiniChefV2.
+    */
+    function emergencyWithdrawLpStake(
+        uint pid
+    ) external returns (string memory _eventName, bytes memory _eventParam) {
+        (uint amount, address lpTokenAddr) = _emergencyWithdraw_LP_Stake(pid);
+
+        _eventName = "LogEmergencyWithdrawLpStake(address,uint256,uint256)";
+        _eventParam = abi.encode(lpTokenAddr, pid, amount);
+    }
+
+    // PNG Staking
+    /**
+    * @notice Deposit PNG in staking contract
+    * @param stakingContract The address of the single PNG staking contract
+    * @param amount The amount of the PNG to deposit.
+    * @param getId ID to retrieve sellAmt.
+    * @param setId ID stores the amount of token brought.
+    */
+    function depositPNGStake(
+        address stakingContract,
+        uint256 amount,
+        uint256 getId,
+        uint256 setId
+    ) external returns (string memory _eventName, bytes memory _eventParam) {
+        uint _amt = getUint(getId, amount);
+
+        _depositPNGStake(stakingContract, _amt);
+
+        setUint(setId, _amt);
+
+        _eventName = "LogDepositPNGStake(address,uint256,uint256,uint256)";
+        _eventParam = abi.encode(stakingContract, _amt, getId, setId);
+    }
+
+    /**
+    * @notice Withdraw PNG staked from staking contract
+    * @param stakingContract The address of the single PNG staking contract
+    * @param amount The amount of the PNG to withdraw.
+    * @param getId ID to retrieve sellAmt.
+    * @param setId ID stores the amount of token brought.
+    */
+    function withdrawPNGStake(
+        address stakingContract,
+        uint256 amount,
+        uint256 getId,
+        uint256 setId
+    ) external returns (string memory _eventName, bytes memory _eventParam) {
+        uint _amt = getUint(getId, amount);
+
+        _withdrawPNGStake(stakingContract, _amt);
+
+        setUint(setId, _amt);
+
+        _eventName = "LogWithdrawPNGStake(address,uint256,uint256,uint256)";
+        _eventParam = abi.encode(stakingContract, _amt, getId, setId);
+    }
+
+    /**
+    * @notice Withdraw all PNG staked from staking contract
+    * @param stakingContract The address of the single PNG staking contract
+    */
+    function exitPNGStake(
+        address stakingContract
+    ) external returns (string memory _eventName, bytes memory _eventParam) {
+        (uint256 exitAmount, uint256 rewardAmount, address rewardToken) = _exitPNGStake(stakingContract);
+
+        _eventName = "LogExitPNGStake(address,uint256,uint256,address)";
+        _eventParam = abi.encode(stakingContract, exitAmount, rewardAmount, rewardToken);
+    }
+
+    /**
+    * @notice Claim rewards from staking contract
+    * @param stakingContract The address of the single PNG staking contract
+    */
+    function claimPNGStakeReward(
+        address stakingContract
+    ) external returns (string memory _eventName, bytes memory _eventParam) {
+        (uint256 rewardAmount, address rewardToken) = _claimPNGStakeReward(stakingContract);
+
+        _eventName = "LogClaimPNGStakeReward(address,uint256,address)";
+        _eventParam = abi.encode(stakingContract, rewardAmount, rewardToken);
+    }
+}
+
+contract ConnectV2PngStakeAvalanche is PangolinStakeResolver {
+    string public constant name = "Pangolin-Stake-v1";
+}
diff --git a/test/avalanche/pangolin/pangolin.test.ts b/test/avalanche/pangolin/pangolin_exchange.test.ts
similarity index 95%
rename from test/avalanche/pangolin/pangolin.test.ts
rename to test/avalanche/pangolin/pangolin_exchange.test.ts
index 97f80eab..d4bd37f9 100644
--- a/test/avalanche/pangolin/pangolin.test.ts
+++ b/test/avalanche/pangolin/pangolin_exchange.test.ts
@@ -1,17 +1,16 @@
 import { expect } from "chai";
 import hre from "hardhat";
 
-const { web3, deployments, waffle, ethers } = hre;
-const { provider, deployContract } = waffle;
+const { waffle, ethers } = hre;
+const { provider } = waffle;
 
 import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector";
 import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2";
 import { encodeSpells } from "../../../scripts/tests/encodeSpells";
 import { getMasterSigner } from "../../../scripts/tests/getMasterSigner";
-import { addLiquidity } from "../../../scripts/tests/addLiquidity";
 import { addresses } from "../../../scripts/tests/avalanche/addresses";
 import { abis } from "../../../scripts/constant/abis";
-import type { Signer, Contract } from "ethers";
+import { Signer, Contract } from "ethers";
 
 import { ConnectV2PngAvalanche__factory } from "../../../typechain";
 
@@ -22,13 +21,13 @@ const PNG_AVAX_LP_ADDRESS = "0xd7538cABBf8605BdE1f4901B47B8D42c61DE0367";
 describe("Pangolin DEX - Avalanche", function () {
     const pangolinConnectorName = "PANGOLIN-TEST-A"
     
-    let dsaWallet0: any;
-    let masterSigner: any;
-    let instaConnectorsV2: any;
-    let pangolinConnector: any;
+    let dsaWallet0: Contract;
+    let masterSigner: Signer;
+    let instaConnectorsV2: Contract;
+    let pangolinConnector: Contract;
     
     const wallets = provider.getWallets()
-    const [wallet0, wallet1, wallet2, wallet3] = wallets
+    const [wallet0, wallet1] = wallets
     before(async () => {
         await hre.network.provider.request({
             method: "hardhat_reset",
@@ -61,12 +60,12 @@ describe("Pangolin DEX - Avalanche", function () {
     it("Should have contracts deployed.", async function () {
         expect(!!instaConnectorsV2.address).to.be.true;
         expect(!!pangolinConnector.address).to.be.true;
-        expect(!!masterSigner.address).to.be.true;
+        expect(!!(await masterSigner.getAddress())).to.be.true;
       });
     
     describe("DSA wallet setup", function () {
         it("Should build DSA v2", async function () {
-            dsaWallet0 = await buildDSAv2(wallet0.address)
+            dsaWallet0 = await buildDSAv2(wallet0.getAddress())
             expect(!!dsaWallet0.address).to.be.true;
         });
 
diff --git a/test/avalanche/pangolin/pangolin_stake.test.ts b/test/avalanche/pangolin/pangolin_stake.test.ts
new file mode 100644
index 00000000..7297b574
--- /dev/null
+++ b/test/avalanche/pangolin/pangolin_stake.test.ts
@@ -0,0 +1,821 @@
+import { expect } from "chai";
+import hre from "hardhat";
+
+const { waffle, ethers } = hre;
+const { provider } = waffle;
+
+import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector";
+import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2";
+import { encodeSpells } from "../../../scripts/tests/encodeSpells";
+import { getMasterSigner } from "../../../scripts/tests/getMasterSigner";
+import { addresses } from "../../../scripts/tests/avalanche/addresses";
+import { abis } from "../../../scripts/constant/abis";
+import { Signer, Contract, BigNumber } from "ethers";
+
+import { ConnectV2PngAvalanche__factory, ConnectV2PngStakeAvalanche__factory } from "../../../typechain";
+
+const PNG_ADDRESS  = "0x60781C2586D68229fde47564546784ab3fACA982";
+const WAVAX_ADDRESS = "0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7";
+const PNG_AVAX_LP_ADDRESS = "0xd7538cABBf8605BdE1f4901B47B8D42c61DE0367";
+const PNG_STAKING_ADDRESS = "0x88afdaE1a9F58Da3E68584421937E5F564A0135b";
+
+describe("Pangolin Stake - Avalanche", function () {
+    const pangolinConnectorName = "PANGOLIN-TEST-A"
+    const pangolinStakeConnectorName = "PANGOLIN-STAKE-TEST-A"
+    
+    let dsaWallet0: Contract;
+    let masterSigner: Signer;
+    let instaConnectorsV2: Contract;
+    let pangolinConnector: Contract;
+    let pangolinStakeConnector: Contract;
+    
+    let PNG: Contract;
+
+    const wallets = provider.getWallets()
+    const [wallet0, wallet1] = wallets
+    before(async () => {
+        await hre.network.provider.request({
+            method: "hardhat_reset",
+            params: [
+                {
+                    forking: {
+                        jsonRpcUrl: `https://api.avax.network/ext/bc/C/rpc`,
+                        blockNumber: 8197390
+                    },
+                },
+            ],
+        });
+
+        PNG = await ethers.getContractAt(
+            abis.basic.erc20, 
+            PNG_ADDRESS
+        );
+
+        masterSigner = await getMasterSigner();
+        instaConnectorsV2 = await ethers.getContractAt(
+            abis.core.connectorsV2,
+            addresses.core.connectorsV2
+        );
+
+        // Deploy and enable Pangolin Connector
+        pangolinConnector = await deployAndEnableConnector({
+            connectorName: pangolinConnectorName,
+            contractArtifact: ConnectV2PngAvalanche__factory,
+            signer: masterSigner,
+            connectors: instaConnectorsV2
+        });
+        console.log("Pangolin Connector address: "+ pangolinConnector.address);
+
+        // Deploy and enable Pangolin Stake Connector
+        pangolinStakeConnector = await deployAndEnableConnector({
+            connectorName: pangolinStakeConnectorName,
+            contractArtifact: ConnectV2PngStakeAvalanche__factory,
+            signer: masterSigner,
+            connectors: instaConnectorsV2
+        });
+        console.log("Pangolin Stake Connector address: "+ pangolinStakeConnector.address);
+    })
+
+    it("Should have contracts deployed.", async function () {
+        expect(!!instaConnectorsV2.address).to.be.true;
+        expect(!!pangolinConnector.address).to.be.true;
+        expect(!!pangolinStakeConnector.address).to.be.true;
+        expect(!!(await masterSigner.getAddress())).to.be.true;
+      });
+    
+    describe("DSA wallet setup", function () {
+        it("Should build DSA v2", async function () {
+            dsaWallet0 = await buildDSAv2(wallet0.getAddress())
+            expect(!!dsaWallet0.address).to.be.true;
+        });
+
+        it("Deposit 10 AVAX into DSA wallet", async function () {
+            await wallet0.sendTransaction({
+                to: dsaWallet0.address,
+                value: ethers.utils.parseEther("10")
+            });
+            expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10"));
+        });
+    });
+
+    describe("Pangolin Staking - LP Stake Test", function () {
+        let lpAmount: BigNumber;
+        let pangolinLPToken: Contract;
+        // Buy 100 PNG and deposity in PNG/AVAX LP
+        before(async () => {
+            const amount = ethers.utils.parseEther("100"); // 100 PNG
+            const int_slippage = 0.03
+            const slippage = ethers.utils.parseEther(int_slippage.toString());
+            const setId = "0";
+    
+            const PangolinRouterABI = [
+                "function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)"
+            ];
+    
+            // Get amount of AVAX for 200 PNG from Pangolin
+            const PangolinRouter = await ethers.getContractAt(
+                PangolinRouterABI, 
+                "0xE54Ca86531e17Ef3616d22Ca28b0D458b6C89106"
+            );
+            const amounts = await PangolinRouter.getAmountsOut(
+                amount, 
+                [
+                    PNG_ADDRESS, 
+                    WAVAX_ADDRESS
+                ]
+            );
+
+            const amtA = amounts[0];
+            const amtB = amounts[1];
+            const unitAmt = (amtB * (1 + int_slippage)) / amtA;
+            const unitAmount = ethers.utils.parseEther(unitAmt.toString());
+
+            const spells = [
+                {
+                    connector: pangolinConnectorName,
+                    method: "buy",
+                    args: [
+                        PNG_ADDRESS, 
+                        "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", 
+                        amount, 
+                        unitAmount, 
+                        0, 
+                        0
+                    ]
+                },
+                {
+                    connector: pangolinConnectorName,
+                    method: "deposit",
+                    args: [
+                        PNG_ADDRESS, 
+                        "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", 
+                        amount, 
+                        unitAmount, 
+                        slippage, 
+                        0, 
+                        setId
+                    ]
+                },
+            ];
+            // Run spell transaction
+            const tx = await dsaWallet0.connect(wallet0).cast(
+                ...encodeSpells(spells), wallet1.address
+            );
+            const receipt = await tx.wait();
+            pangolinLPToken = await ethers.getContractAt(
+                abis.basic.erc20, 
+                PNG_AVAX_LP_ADDRESS
+            );
+        });
+
+        it("Check if has PNG/AVAX LP", async function () {
+            const pangolinPoolAVAXBalance = await pangolinLPToken.balanceOf(dsaWallet0.address);
+            expect(pangolinPoolAVAXBalance, `Pangolin PNG/AVAX LP greater than 0`).to.be.gt(0);
+            console.log("PNG/AVAX LP: ", ethers.utils.formatUnits(pangolinPoolAVAXBalance, "ether").toString())
+            lpAmount = pangolinPoolAVAXBalance;
+        });
+        
+        it("Check if all functions reverts by: Invalid pid!", async function () {
+            const pid = BigNumber.from("999999999999");
+            const amount = ethers.utils.parseEther("1");
+            const getId = 0;
+            const setId = 0;
+
+            let spells = [
+                {
+                    connector: pangolinStakeConnectorName,
+                    method: "depositLpStake",
+                    args: [
+                        pid, 
+                        amount, 
+                        getId, 
+                        setId
+                    ]
+                }
+            ];
+            await expect(
+                dsaWallet0.connect(wallet0).cast(
+                  ...encodeSpells(spells),
+                  wallet1.address
+                )
+            ).to.be.revertedWith("Invalid pid!");
+
+            spells[0].method = "withdrawLpStake"
+            await expect(
+                dsaWallet0.connect(wallet0).cast(
+                  ...encodeSpells(spells),
+                  wallet1.address
+                )
+            ).to.be.revertedWith("Invalid pid!");
+
+            spells[0].method = "withdrawAndClaimLpRewards"
+            await expect(
+                dsaWallet0.connect(wallet0).cast(
+                  ...encodeSpells(spells),
+                  wallet1.address
+                )
+            ).to.be.revertedWith("Invalid pid!");
+
+            spells = [
+                {
+                    connector: pangolinStakeConnectorName,
+                    method: "claimLpRewards",
+                    args: [
+                        pid
+                    ]
+                }
+            ];
+            await expect(
+                dsaWallet0.connect(wallet0).cast(
+                  ...encodeSpells(spells),
+                  wallet1.address
+                )
+            ).to.be.revertedWith("Invalid pid!");
+
+            spells[0].method = "emergencyWithdrawLpStake"
+            await expect(
+                dsaWallet0.connect(wallet0).cast(
+                  ...encodeSpells(spells),
+                  wallet1.address
+                )
+            ).to.be.revertedWith("Invalid pid!");
+        });
+
+        it("Check if all functions reverts by: 'Invalid amount, amount cannot be 0'", async function () {
+            let spells = [
+                {
+                    connector: pangolinStakeConnectorName,
+                    method: "depositLpStake",
+                    args: [
+                        0,
+                        0,
+                        0,
+                        0
+                    ]
+                }
+            ];
+            await expect(
+                dsaWallet0.connect(wallet0).cast(
+                  ...encodeSpells(spells),
+                  wallet1.address
+                )
+            ).to.be.revertedWith("Invalid amount, amount cannot be 0");
+
+            spells[0].method = "withdrawLpStake"
+            await expect(
+                dsaWallet0.connect(wallet0).cast(
+                  ...encodeSpells(spells),
+                  wallet1.address
+                )
+            ).to.be.revertedWith("Invalid amount, amount cannot be 0");
+
+            spells[0].method = "withdrawLpStake"
+            await expect(
+                dsaWallet0.connect(wallet0).cast(
+                  ...encodeSpells(spells),
+                  wallet1.address
+                )
+            ).to.be.revertedWith("Invalid amount, amount cannot be 0");
+
+            spells[0].method = "withdrawAndClaimLpRewards"
+            await expect(
+                dsaWallet0.connect(wallet0).cast(
+                  ...encodeSpells(spells),
+                  wallet1.address
+                )
+            ).to.be.revertedWith("Invalid amount, amount cannot be 0");
+        });
+
+        describe("depositLpStake function", function () {
+            it("Check if depositLpStake function reverts by: Invalid amount, amount greater than balance of LP token", async function () {
+                const amount = lpAmount.mul(2);
+                const spells = [
+                    {
+                        connector: pangolinStakeConnectorName,
+                        method: "depositLpStake",
+                        args: [
+                            0,
+                            amount,
+                            0,
+                            0
+                        ]
+                    }
+                ];
+                await expect(
+                    dsaWallet0.connect(wallet0).cast(
+                      ...encodeSpells(spells),
+                      wallet1.address
+                    )
+                ).to.be.revertedWith("Invalid amount, amount greater than balance of LP token");
+            });
+
+            it("Check if success in depositLpStake", async function () {
+                const spells = [
+                    {
+                        connector: pangolinStakeConnectorName,
+                        method: "depositLpStake",
+                        args: [
+                            0,
+                            lpAmount,
+                            0,
+                            0
+                        ]
+                    }
+                ];
+                await expect(
+                    dsaWallet0.connect(wallet0).cast(
+                      ...encodeSpells(spells),
+                      wallet1.address
+                    )
+                ).to.be.not.reverted;
+                // Check if PNG/AVAX LP is equal 0
+                const balance = await pangolinLPToken.balanceOf(dsaWallet0.address);
+                expect(balance).to.be.eq(0);
+            });
+
+            it("Check if depositLpStake function reverts by: Invalid LP token balance", async function () {
+                const spells = [
+                    {
+                        connector: pangolinStakeConnectorName,
+                        method: "depositLpStake",
+                        args: [
+                            0,
+                            lpAmount,
+                            0,
+                            0
+                        ]
+                    }
+                ];
+                await expect(
+                    dsaWallet0.connect(wallet0).cast(
+                      ...encodeSpells(spells),
+                      wallet1.address
+                    )
+                ).to.be.revertedWith("Invalid LP token balance");
+            });
+        });
+
+        describe("claimLpRewards function", function () {
+            it("Check if success in claimLpRewards", async function () {
+                // Increase Time in 20 seconds
+                await hre.network.provider.send("evm_increaseTime", [20]);
+                // Mine new block
+                await hre.network.provider.send("evm_mine");
+                const spells = [
+                    {
+                        connector: pangolinStakeConnectorName,
+                        method: "claimLpRewards",
+                        args: [0]
+                    }
+                ];
+                await expect(
+                    dsaWallet0.connect(wallet0).cast(
+                      ...encodeSpells(spells),
+                      wallet1.address
+                    )
+                ).to.be.not.reverted;
+                // Checks if the wallet has more than 100 PNG
+                const balance = await PNG.balanceOf(dsaWallet0.address);
+                expect(balance).to.be.gt(0);
+            });
+        });
+
+        describe("withdrawLpStake function", function () {
+            it("Check if withdrawLpStake function reverts by: Invalid amount, amount greater than balance of staking", async function () {
+                const amount = lpAmount.mul(2);
+                const spells = [
+                    {
+                        connector: pangolinStakeConnectorName,
+                        method: "withdrawLpStake",
+                        args: [
+                            0,
+                            amount,
+                            0,
+                            0
+                        ]
+                    }
+                ];
+                await expect(
+                    dsaWallet0.connect(wallet0).cast(
+                      ...encodeSpells(spells),
+                      wallet1.address
+                    )
+                ).to.be.revertedWith("Invalid amount, amount greater than balance of staking");
+            });
+
+            it("Check if success in withdrawLpStake", async function () {
+                const spells = [
+                    {
+                        connector: pangolinStakeConnectorName,
+                        method: "withdrawLpStake",
+                        args: [
+                            0,
+                            lpAmount.div(2),
+                            0,
+                            0
+                        ]
+                    }
+                ];
+                await expect(
+                    dsaWallet0.connect(wallet0).cast(
+                      ...encodeSpells(spells),
+                      wallet1.address
+                    )
+                ).to.be.not.reverted;
+                // Check if PNG/AVAX LP is equal 0
+                const balance = await pangolinLPToken.balanceOf(dsaWallet0.address);
+                expect(balance).to.be.eq(lpAmount.div(2));
+            });
+        });
+
+        describe("withdrawAndClaimLpRewards function", function () {
+            it("Check if withdrawAndClaimLpRewards function reverts by: Invalid amount, amount greater than balance of staking", async function () {
+                const amount = lpAmount.mul(2);
+                const spells = [
+                    {
+                        connector: pangolinStakeConnectorName,
+                        method: "withdrawAndClaimLpRewards",
+                        args: [
+                            0,
+                            amount,
+                            0,
+                            0
+                        ]
+                    }
+                ];
+                await expect(
+                    dsaWallet0.connect(wallet0).cast(
+                      ...encodeSpells(spells),
+                      wallet1.address
+                    )
+                ).to.be.revertedWith("Invalid amount, amount greater than balance of staking");
+            });
+
+            it("Check if success in withdrawAndClaimLpRewards", async function () {
+                let balance = await pangolinLPToken.balanceOf(dsaWallet0.address);
+                const png_balance = await PNG.balanceOf(dsaWallet0.address);
+                const amount = lpAmount.sub(balance)
+                const spells = [
+                    {
+                        connector: pangolinStakeConnectorName,
+                        method: "withdrawAndClaimLpRewards",
+                        args: [
+                            0,
+                            amount,
+                            0,
+                            0
+                        ]
+                    }
+                ];
+                await expect(
+                    dsaWallet0.connect(wallet0).cast(
+                      ...encodeSpells(spells),
+                      wallet1.address
+                    )
+                ).to.be.not.reverted;
+                // Check if PNG/AVAX LP is equal 0
+                balance = await pangolinLPToken.balanceOf(dsaWallet0.address);
+                expect(balance).to.be.eq(lpAmount);
+                const new_png_balance = await PNG.balanceOf(dsaWallet0.address);
+                expect(new_png_balance).to.be.gt(png_balance);
+            });
+        });
+
+        describe("emergencyWithdrawLpStake function", function () {
+            // Deposit LP again
+            before(async () => {
+                const spells = [
+                    {
+                        connector: pangolinStakeConnectorName,
+                        method: "depositLpStake",
+                        args: [
+                            0,
+                            lpAmount,
+                            0,
+                            0
+                        ]
+                    }
+                ];
+                await dsaWallet0.connect(wallet0).cast(
+                    ...encodeSpells(spells),
+                    wallet1.address
+                )
+            });
+
+            it("Check if success in emergencyWithdrawLpStake", async function () {
+                let balance = await pangolinLPToken.balanceOf(dsaWallet0.address);
+                const amount = lpAmount.sub(balance)
+                const spells = [
+                    {
+                        connector: pangolinStakeConnectorName,
+                        method: "emergencyWithdrawLpStake",
+                        args: [0]
+                    }
+                ];
+                await expect(
+                    dsaWallet0.connect(wallet0).cast(
+                      ...encodeSpells(spells),
+                      wallet1.address
+                    )
+                ).to.be.not.reverted;
+                // Check if PNG/AVAX LP is equal 0
+                balance = await pangolinLPToken.balanceOf(dsaWallet0.address);
+                expect(balance).to.be.eq(lpAmount);
+            });
+        });
+    });
+
+    describe("Pangolin Staking - Single Stake Test (PNG)", function () {
+        let pngToken: Contract;
+        let stakingContract: Contract;
+        let stakingBalance: BigNumber;
+        before(async () => {
+            const amount = ethers.utils.parseEther("100"); // 100 PNG
+            const int_slippage = 0.03
+    
+            const PangolinRouterABI = [
+                "function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)"
+            ];
+    
+            // Get amount of AVAX for 200 PNG from Pangolin
+            const PangolinRouter = await ethers.getContractAt(
+                PangolinRouterABI, 
+                "0xE54Ca86531e17Ef3616d22Ca28b0D458b6C89106"
+            );
+            const amounts = await PangolinRouter.getAmountsOut(
+                amount, 
+                [
+                    PNG_ADDRESS, 
+                    WAVAX_ADDRESS
+                ]
+            );
+
+            const amtA = amounts[0];
+            const amtB = amounts[1];
+            const unitAmt = (amtB * (1 + int_slippage)) / amtA;
+            const unitAmount = ethers.utils.parseEther(unitAmt.toString());
+
+            const spells = [
+                {
+                    connector: pangolinConnectorName,
+                    method: "buy",
+                    args: [
+                        PNG_ADDRESS, 
+                        "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", 
+                        amount, 
+                        unitAmount, 
+                        0, 
+                        0
+                    ]
+                }
+            ];
+            // Run spell transaction
+            const tx = await dsaWallet0.connect(wallet0).cast(
+                ...encodeSpells(spells), wallet1.address
+            );
+            const receipt = await tx.wait();
+
+            pngToken = await ethers.getContractAt(abis.basic.erc20, PNG_ADDRESS);
+            stakingContract = await ethers.getContractAt(abis.basic.erc20, PNG_STAKING_ADDRESS);
+        });
+
+        it("Check if has 100 PNG", async function () {
+            const amount = ethers.utils.parseEther("100");
+            const pngBalance = await pngToken.balanceOf(dsaWallet0.address);
+            expect(pngBalance, `PNG Token is equal 100`).to.be.gt(amount.toString());
+        });
+    
+        it("Check if some functions reverts by: Invalid amount, amount cannot be 0", async function () {
+            const amount = 0;
+            const getId = 0;
+            const setId = 0;
+            let spells = [
+                {
+                    connector: pangolinStakeConnectorName,
+                    method: "depositPNGStake",
+                    args: [
+                        PNG_STAKING_ADDRESS, 
+                        amount, 
+                        getId, 
+                        setId
+                    ]
+                }
+            ];
+            await expect(
+                dsaWallet0.connect(wallet0).cast(
+                  ...encodeSpells(spells),
+                  wallet1.address
+                )
+            ).to.be.revertedWith("Invalid amount, amount cannot be 0");
+
+            spells[0].method = "withdrawPNGStake"
+            await expect(
+                dsaWallet0.connect(wallet0).cast(
+                  ...encodeSpells(spells),
+                  wallet1.address
+                )
+            ).to.be.revertedWith("Invalid amount, amount cannot be 0");
+        });
+
+        describe("depositPNGStake function", function () {
+            it("Check if reverts by: Invalid amount, amount greater than balance of PNG", async function () {
+                const amount = ethers.utils.parseEther("200")
+                let spells = [
+                    {
+                        connector: pangolinStakeConnectorName,
+                        method: "depositPNGStake",
+                        args: [
+                            PNG_STAKING_ADDRESS, 
+                            amount, 
+                            0, 
+                            0
+                        ]
+                    }
+                ];
+                await expect(
+                    dsaWallet0.connect(wallet0).cast(
+                      ...encodeSpells(spells),
+                      wallet1.address
+                    )
+                ).to.be.revertedWith("Invalid amount, amount greater than balance of PNG");
+            });
+
+            it("Check if success in depositPNGStake", async function () {
+                const amount = await pngToken.balanceOf(dsaWallet0.address);
+                let spells = [
+                    {
+                        connector: pangolinStakeConnectorName,
+                        method: "depositPNGStake",
+                        args: [
+                            PNG_STAKING_ADDRESS, 
+                            amount, 
+                            0, 
+                            0
+                        ]
+                    }
+                ];
+                await expect(
+                    dsaWallet0.connect(wallet0).cast(
+                      ...encodeSpells(spells),
+                      wallet1.address
+                    )
+                ).to.be.not.reverted;
+                const new_png_balance = await pngToken.balanceOf(dsaWallet0.address);
+                expect(new_png_balance).to.be.eq(0);
+                const staking_balance = await stakingContract.balanceOf(dsaWallet0.address);
+                expect(staking_balance).to.be.gt(0);
+                stakingBalance = staking_balance
+            });
+
+            it("Check if reverts by: Invalid PNG balance", async function () {
+                const amount = ethers.utils.parseEther("100")
+                let spells = [
+                    {
+                        connector: pangolinStakeConnectorName,
+                        method: "depositPNGStake",
+                        args: [
+                            PNG_STAKING_ADDRESS, 
+                            amount, 
+                            0, 
+                            0
+                        ]
+                    }
+                ];
+                await expect(
+                    dsaWallet0.connect(wallet0).cast(
+                      ...encodeSpells(spells),
+                      wallet1.address
+                    )
+                ).to.be.revertedWith("Invalid PNG balance");
+            });
+        });
+
+        describe("withdrawPNGStake function", function () {
+            it("Check if reverts by: Invalid amount, amount greater than balance of staking", async function () {
+                const amount = ethers.utils.parseEther("200")
+                let spells = [
+                    {
+                        connector: pangolinStakeConnectorName,
+                        method: "withdrawPNGStake",
+                        args: [
+                            PNG_STAKING_ADDRESS, 
+                            amount, 
+                            0, 
+                            0
+                        ]
+                    }
+                ];
+                await expect(
+                    dsaWallet0.connect(wallet0).cast(
+                      ...encodeSpells(spells),
+                      wallet1.address
+                    )
+                ).to.be.revertedWith("Invalid amount, amount greater than balance of staking");
+            });
+
+            it("Check if success in withdrawPNGStake", async function () {
+                const amount = ethers.utils.parseEther("50");
+                let spells = [
+                    {
+                        connector: pangolinStakeConnectorName,
+                        method: "withdrawPNGStake",
+                        args: [
+                            PNG_STAKING_ADDRESS, 
+                            amount, 
+                            0, 
+                            0
+                        ]
+                    }
+                ];
+                await expect(
+                    dsaWallet0.connect(wallet0).cast(
+                      ...encodeSpells(spells),
+                      wallet1.address
+                    )
+                ).to.be.not.reverted;
+
+                const balance = await pngToken.balanceOf(dsaWallet0.address);
+                expect(balance).to.be.eq(amount);
+            });
+        });
+
+        describe("claimPNGStakeReward function", function () {
+            it("Check if success in claimPNGStakeReward", async function () {
+                // Increase Time in 20 seconds
+                await hre.network.provider.send("evm_increaseTime", [20]);
+                // Mine new block
+                await hre.network.provider.send("evm_mine");
+                const amount = ethers.utils.parseEther("50");
+                let spells = [
+                    {
+                        connector: pangolinStakeConnectorName,
+                        method: "claimPNGStakeReward",
+                        args: [PNG_STAKING_ADDRESS]
+                    }
+                ];
+                await expect(
+                    dsaWallet0.connect(wallet0).cast(
+                      ...encodeSpells(spells),
+                      wallet1.address
+                    )
+                ).to.be.not.reverted;
+
+                const balance = await pngToken.balanceOf(dsaWallet0.address);
+                expect(balance).to.be.gt(amount);
+            });
+
+            it("Check if reverts by: No rewards to claim", async function () {
+                let spells = [
+                    {
+                        connector: pangolinStakeConnectorName,
+                        method: "claimPNGStakeReward",
+                        args: [PNG_STAKING_ADDRESS]
+                    }
+                ];
+                await expect(
+                    dsaWallet0.connect(wallet0).cast(
+                      ...encodeSpells(spells),
+                      wallet1.address
+                    )
+                ).to.be.revertedWith("No rewards to claim");
+            });
+        });
+
+        describe("exitPNGStake function", function () {
+            it("Check if success in exitPNGStake", async function () {
+                let spells = [
+                    {
+                        connector: pangolinStakeConnectorName,
+                        method: "exitPNGStake",
+                        args: [PNG_STAKING_ADDRESS]
+                    }
+                ];
+                await expect(
+                    dsaWallet0.connect(wallet0).cast(
+                      ...encodeSpells(spells),
+                      wallet1.address
+                    )
+                ).to.be.not.reverted;
+
+                const balance = await stakingContract.balanceOf(dsaWallet0.address);
+                expect(balance).to.be.eq(0);
+            });
+
+            it("Check if reverts by: No balance to exit", async function () {
+                let spells = [
+                    {
+                        connector: pangolinStakeConnectorName,
+                        method: "exitPNGStake",
+                        args: [PNG_STAKING_ADDRESS]
+                    }
+                ];
+                await expect(
+                    dsaWallet0.connect(wallet0).cast(
+                      ...encodeSpells(spells),
+                      wallet1.address
+                    )
+                ).to.be.revertedWith("No balance to exit");
+            });
+        });
+    });
+});