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
This commit is contained in:
Pedro 2021-12-20 17:50:05 -04:00
parent 358b9bfebb
commit 7884bb31e0
7 changed files with 1298 additions and 12 deletions

View File

@ -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.

View File

@ -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
);
}

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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";
}

View File

@ -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;
});

View File

@ -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");
});
});
});
});