Skip to content

Commit 580a258

Browse files
committed
added router tests
1 parent ab36769 commit 580a258

File tree

6 files changed

+255
-1
lines changed

6 files changed

+255
-1
lines changed

contracts/Mock/MockERC20.sol

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: GPL-3.0-only
22
pragma solidity 0.7.6;
33

4+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
45
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
56

67
contract MockERC20 is ERC20 {
@@ -14,3 +15,37 @@ contract MockBAL is ERC20 {
1415
ERC20._mint(recipient, amount);
1516
}
1617
}
18+
19+
contract MockCharmLiqToken is ERC20 {
20+
address public token0;
21+
address public token1;
22+
23+
constructor(address _token0, address _token1) ERC20("MockCharmLP", "MockCharmLP") {
24+
token0 = _token0;
25+
token1 = _token1;
26+
}
27+
28+
function deposit(
29+
uint256 amount0,
30+
uint256 amount1,
31+
uint256 minAmount0,
32+
uint256 minAmount1,
33+
address to
34+
)
35+
external
36+
returns (
37+
uint256 lpAmt,
38+
uint256 actual0,
39+
uint256 actual1
40+
)
41+
{
42+
require(amount0 >= minAmount0, "Insufficient token0");
43+
require(amount1 >= minAmount1, "Insufficient token1");
44+
lpAmt = amount0 + amount1;
45+
_mint(to, lpAmt);
46+
actual0 = amount0;
47+
actual1 = amount1;
48+
IERC20(token0).transferFrom(msg.sender, address(this), actual0);
49+
IERC20(token1).transferFrom(msg.sender, address(this), actual1);
50+
}
51+
}

contracts/Mock/MockGeyser.sol

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// SPDX-License-Identifier: GPL-3.0-only
2+
pragma solidity 0.7.6;
3+
pragma abicoder v2;
4+
5+
import {IGeyser} from "../Geyser.sol";
6+
7+
contract MockGeyser {
8+
address public stakingToken;
9+
10+
constructor(address _stakingToken) {
11+
stakingToken = _stakingToken;
12+
}
13+
14+
function getGeyserData() external view returns (IGeyser.GeyserData memory) {
15+
IGeyser.RewardSchedule[] memory rewardSchedules;
16+
return
17+
IGeyser.GeyserData({
18+
stakingToken: stakingToken,
19+
rewardToken: address(0),
20+
rewardPool: address(0),
21+
rewardScaling: IGeyser.RewardScaling({floor: 0, ceiling: 0, time: 0}),
22+
rewardSharesOutstanding: 0,
23+
totalStake: 0,
24+
totalStakeUnits: 0,
25+
lastUpdate: 0,
26+
rewardSchedules: rewardSchedules
27+
});
28+
}
29+
30+
event LogStaked(address vault, uint256 amount, bytes permission);
31+
32+
function stake(
33+
address vault,
34+
uint256 amount,
35+
bytes calldata permission
36+
) external {
37+
emit LogStaked(vault, amount, permission);
38+
}
39+
}

contracts/Mock/MockVaultFactory.sol

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// SPDX-License-Identifier: GPL-3.0-only
2+
pragma solidity 0.7.6;
3+
pragma abicoder v2;
4+
5+
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
6+
7+
contract MockVaultFactory is ERC721("MockVaultFactory", "MVF") {
8+
uint256 public nextVaultId;
9+
10+
constructor() {
11+
nextVaultId = 1;
12+
}
13+
14+
function create2(
15+
bytes calldata, /*args*/
16+
bytes32 /*salt*/
17+
) external returns (address) {
18+
uint256 vaultId = nextVaultId;
19+
nextVaultId++;
20+
_mint(msg.sender, vaultId);
21+
return address(uint160(vaultId));
22+
}
23+
}

contracts/Router/CharmGeyserRouter.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ contract CharmGeyserRouter is GeyserRouter {
5757
// transfers liquidity tokens directly to the vault
5858
_checkAndApproveMax(depositToken0, address(charm), d.token0Amt);
5959
_checkAndApproveMax(depositToken1, address(charm), d.token1Amt);
60-
(uint256 lpAmt,,) = charm.deposit(d.token0Amt, d.token1Amt, d.token0MinAmt, d.token1MinAmt, vault);
60+
(uint256 lpAmt, , ) = charm.deposit(d.token0Amt, d.token1Amt, d.token0MinAmt, d.token1MinAmt, vault);
6161

6262
// Stake liquidity tokens from the vault
6363
IGeyser(geyser).stake(vault, lpAmt, permission);

contracts/Router/GeyserRouter.sol

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,9 @@ contract GeyserRouter is IERC721Receiver {
135135
IGeyser(request.geyser).unstakeAndClaim(request.vault, request.amount, request.permission);
136136
}
137137
}
138+
139+
function unstakeAndRestake(UnstakeRequest calldata r1, StakeRequest calldata r2) external {
140+
IGeyser(r1.geyser).stake(r1.vault, r1.amount, r1.permission);
141+
IGeyser(r2.geyser).unstakeAndClaim(r2.vault, r2.amount, r2.permission);
142+
}
138143
}

test/CharmGeyserRouter.ts

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { ethers } from 'hardhat'
2+
import { expect } from 'chai'
3+
import { Signer, Contract, BigNumber } from 'ethers'
4+
5+
describe('CharmGeyserRouter', function () {
6+
let deployer: Signer
7+
let user: Signer
8+
let vaultOwner: Signer
9+
let token0: Contract
10+
let token1: Contract
11+
let charmLiqToken: Contract
12+
let geyser: Contract
13+
let vaultFactory: Contract
14+
let router: Contract
15+
16+
beforeEach(async () => {
17+
;[deployer, user, vaultOwner] = await ethers.getSigners()
18+
19+
// Deploy mock tokens
20+
const MockERC20 = await ethers.getContractFactory('MockERC20')
21+
token0 = await MockERC20.connect(deployer).deploy(await user.getAddress(), ethers.utils.parseEther('1000000'))
22+
token1 = await MockERC20.connect(deployer).deploy(await user.getAddress(), ethers.utils.parseEther('1000000'))
23+
await token0.deployed()
24+
await token1.deployed()
25+
26+
// Deploy mock Charm LP Token
27+
const MockCharmLiqToken = await ethers.getContractFactory('MockCharmLiqToken')
28+
charmLiqToken = await MockCharmLiqToken.deploy(token0.address, token1.address)
29+
await charmLiqToken.deployed()
30+
31+
// Deploy mock Geyser
32+
const MockGeyser = await ethers.getContractFactory('MockGeyser')
33+
geyser = await MockGeyser.deploy(charmLiqToken.address)
34+
await geyser.deployed()
35+
36+
// Deploy mock Vault Factory
37+
const MockVaultFactory = await ethers.getContractFactory('MockVaultFactory')
38+
vaultFactory = await MockVaultFactory.deploy()
39+
await vaultFactory.deployed()
40+
41+
// Deploy CharmGeyserRouter
42+
const CharmGeyserRouter = await ethers.getContractFactory('CharmGeyserRouter')
43+
router = await CharmGeyserRouter.deploy()
44+
await router.deployed()
45+
})
46+
47+
describe('createLiqAndStake', function () {
48+
it('should create liquidity and stake correctly', async () => {
49+
const token0Amount = ethers.utils.parseEther('100')
50+
const token1Amount = ethers.utils.parseEther('200')
51+
52+
await token0.connect(user).approve(router.address, token0Amount)
53+
await token1.connect(user).approve(router.address, token1Amount)
54+
const userInitialBal0: BigNumber = await token0.balanceOf(await user.getAddress())
55+
const userInitialBal1: BigNumber = await token1.balanceOf(await user.getAddress())
56+
57+
// dummy vault address
58+
const vault = ethers.Wallet.createRandom().address
59+
60+
const liqPayload = {
61+
token0Amt: token0Amount,
62+
token1Amt: token1Amount,
63+
token0MinAmt: ethers.utils.parseEther('90'),
64+
token1MinAmt: ethers.utils.parseEther('180'),
65+
}
66+
67+
await expect(router.connect(user).createLiqAndStake(geyser.address, vault, '0x', liqPayload))
68+
.to.emit(geyser, 'LogStaked')
69+
.withArgs(vault, token0Amount.add(token1Amount), '0x')
70+
71+
// After staking, router should have transferred all tokens out
72+
expect(await token0.balanceOf(router.address)).to.equal(0)
73+
expect(await token1.balanceOf(router.address)).to.equal(0)
74+
75+
const userFinalBal0 = await token0.balanceOf(await user.getAddress())
76+
const userFinalBal1 = await token1.balanceOf(await user.getAddress())
77+
expect(userInitialBal0.sub(userFinalBal0)).to.equal(token0Amount)
78+
expect(userInitialBal1.sub(userFinalBal1)).to.equal(token1Amount)
79+
})
80+
})
81+
82+
describe('create2VaultCreateLiqAndStake', function () {
83+
it('should create a vault, transfer ownership, create liquidity and stake', async () => {
84+
const token0Amount = ethers.utils.parseEther('50')
85+
const token1Amount = ethers.utils.parseEther('100')
86+
87+
await token0.connect(user).approve(router.address, token0Amount)
88+
await token1.connect(user).approve(router.address, token1Amount)
89+
90+
const liqPayload = {
91+
token0Amt: token0Amount,
92+
token1Amt: token1Amount,
93+
token0MinAmt: ethers.utils.parseEther('40'),
94+
token1MinAmt: ethers.utils.parseEther('80'),
95+
}
96+
97+
const salt = ethers.utils.formatBytes32String('testSalt')
98+
const args = [geyser.address, vaultFactory.address, await vaultOwner.getAddress(), salt, '0x', liqPayload]
99+
const vault = await router.connect(user).callStatic.create2VaultCreateLiqAndStake(...args)
100+
await expect(router.connect(user).create2VaultCreateLiqAndStake(...args))
101+
.to.emit(geyser, 'LogStaked')
102+
.withArgs(vault, token0Amount.add(token1Amount), '0x')
103+
expect(await vaultFactory.ownerOf(1)).to.equal(await vaultOwner.getAddress())
104+
})
105+
})
106+
107+
describe('Approvals', function () {
108+
it('should approve max if current allowance is insufficient', async () => {
109+
const token0Amount = ethers.utils.parseEther('10')
110+
const token1Amount = ethers.utils.parseEther('20')
111+
await token0.connect(user).approve(router.address, token0Amount)
112+
await token1.connect(user).approve(router.address, token1Amount)
113+
114+
const vault = ethers.Wallet.createRandom().address
115+
116+
const liqPayload = {
117+
token0Amt: token0Amount,
118+
token1Amt: token1Amount,
119+
token0MinAmt: ethers.utils.parseEther('9'),
120+
token1MinAmt: ethers.utils.parseEther('18'),
121+
}
122+
123+
expect(await token0.allowance(router.address, charmLiqToken.address)).to.equal(0)
124+
expect(await token1.allowance(router.address, charmLiqToken.address)).to.equal(0)
125+
126+
await router.connect(user).createLiqAndStake(geyser.address, vault, '0x', liqPayload)
127+
128+
const maxUint = ethers.constants.MaxUint256
129+
expect(await token0.allowance(router.address, charmLiqToken.address)).to.equal(maxUint.sub(liqPayload.token0Amt))
130+
expect(await token1.allowance(router.address, charmLiqToken.address)).to.equal(maxUint.sub(liqPayload.token1Amt))
131+
})
132+
})
133+
134+
describe('Edge cases', function () {
135+
it('should handle zero amounts gracefully', async () => {
136+
await token0.connect(user).approve(router.address, 0)
137+
await token1.connect(user).approve(router.address, 0)
138+
139+
const vault = ethers.Wallet.createRandom().address
140+
const liqPayload = {
141+
token0Amt: 0,
142+
token1Amt: 0,
143+
token0MinAmt: 0,
144+
token1MinAmt: 0,
145+
}
146+
147+
await expect(router.connect(user).createLiqAndStake(geyser.address, vault, '0x', liqPayload))
148+
.to.emit(geyser, 'LogStaked')
149+
.withArgs(vault, 0, '0x')
150+
})
151+
})
152+
})

0 commit comments

Comments
 (0)