Skip to content

Commit

Permalink
router tests
Browse files Browse the repository at this point in the history
  • Loading branch information
aalavandhan committed Dec 20, 2024
1 parent d9ec06d commit 905e3af
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 9 deletions.
29 changes: 28 additions & 1 deletion contracts/Mock/MockERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,37 @@ pragma solidity 0.7.6;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MockERC20 is ERC20 {
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/drafts/IERC20Permit.sol";

contract MockERC20 is ERC20, IERC20Permit {
mapping(address => uint256) public override nonces;

constructor(address recipient, uint256 amount) ERC20("MockERC20", "MockERC20") {
ERC20._mint(recipient, amount);
}

// Hard-coded DOMAIN_SEPARATOR for testing purposes only.
// In a real implementation, this would be derived from EIP-712 parameters.
function DOMAIN_SEPARATOR() external pure override returns (bytes32) {
return 0x00;
}

function permit(
address owner,
address spender,
uint256 value,
uint256, // deadline - ignored in mock
uint8, // v - ignored in mock
bytes32, // r - ignored in mock
bytes32 // s - ignored in mock
) external override {
// For testing, ignore signature checks and deadline.
// Simply set allowance directly.
_approve(owner, spender, value);
// Increment nonce as if a successful permit was used.
nonces[owner]++;
}
}

contract MockBAL is ERC20 {
Expand Down
13 changes: 11 additions & 2 deletions contracts/Mock/MockGeyser.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,22 @@ contract MockGeyser {
});
}

event LogStaked(address vault, uint256 amount, bytes permission);
event Staked(address vault, uint256 amount, bytes permission);
event UnstakedAndClaimed(address vault, uint256 amount, bytes permission);

function stake(
address vault,
uint256 amount,
bytes calldata permission
) external {
emit LogStaked(vault, amount, permission);
emit Staked(vault, amount, permission);
}

function unstakeAndClaim(
address vault,
uint256 amount,
bytes calldata permission
) external {
emit UnstakedAndClaimed(vault, amount, permission);
}
}
2 changes: 1 addition & 1 deletion contracts/Mock/MockVaultFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ contract MockVaultFactory is ERC721("MockVaultFactory", "MVF") {
) external returns (address) {
uint256 vaultId = nextVaultId;
nextVaultId++;
_mint(msg.sender, vaultId);
ERC721._safeMint(msg.sender, vaultId);
return address(uint160(vaultId));
}
}
12 changes: 10 additions & 2 deletions frontend/src/sdk/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,11 @@ export const approveCreateDepositStake = async (geyserAddress: string, amount: B
const token = new Contract(tokenAddress, ERC20_ABI, signer)

const salt = randomBytes(32)
const vaultAddress = await router.callStatic.create2Vault(config.VaultFactory.address, salt)
const vaultAddress = await router.callStatic.create2Vault(
config.VaultFactory.address,
salt,
await signer.getAddress(),
)
const vault = new Contract(vaultAddress, config.VaultTemplate.abi, signer)
const lockPermission = await signPermission('Lock', vault, signer, geyserAddress, token.address, amount, '0')
const args = [geyserAddress, config.VaultFactory.address, await signer.getAddress(), amount, salt, lockPermission]
Expand Down Expand Up @@ -186,7 +190,11 @@ export const permitCreateDepositStake = async (geyserAddress: string, amount: Bi
}

const salt = randomBytes(32)
const vaultAddress = await router.callStatic.create2Vault(config.VaultFactory.address, salt)
const vaultAddress = await router.callStatic.create2Vault(
config.VaultFactory.address,
salt,
await signer.getAddress(),
)
const vault = new Contract(vaultAddress, config.VaultTemplate.address, signer)

const permit = await signPermitEIP2612(signer, tokenAddress, router.address, amount, deadline)
Expand Down
6 changes: 3 additions & 3 deletions test/CharmGeyserRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe('CharmGeyserRouter', function () {
}

await expect(router.connect(user).createLiqAndStake(geyser.address, vault, '0x', liqPayload))
.to.emit(geyser, 'LogStaked')
.to.emit(geyser, 'Staked')
.withArgs(vault, token0Amount.add(token1Amount), '0x')

// After staking, router should have transferred all tokens out
Expand Down Expand Up @@ -98,7 +98,7 @@ describe('CharmGeyserRouter', function () {
const args = [geyser.address, vaultFactory.address, await vaultOwner.getAddress(), salt, '0x', liqPayload]
const vault = await router.connect(user).callStatic.create2VaultCreateLiqAndStake(...args)
await expect(router.connect(user).create2VaultCreateLiqAndStake(...args))
.to.emit(geyser, 'LogStaked')
.to.emit(geyser, 'Staked')
.withArgs(vault, token0Amount.add(token1Amount), '0x')
expect(await vaultFactory.ownerOf(1)).to.equal(await vaultOwner.getAddress())
})
Expand Down Expand Up @@ -145,7 +145,7 @@ describe('CharmGeyserRouter', function () {
}

await expect(router.connect(user).createLiqAndStake(geyser.address, vault, '0x', liqPayload))
.to.emit(geyser, 'LogStaked')
.to.emit(geyser, 'Staked')
.withArgs(vault, 0, '0x')
})
})
Expand Down
224 changes: 224 additions & 0 deletions test/GeyserRouter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import { expect } from 'chai'
import { ethers } from 'hardhat'
import { Signer, Contract, BigNumber } from 'ethers'

describe.only('GeyserRouter', function () {
let deployer: Signer
let user: Signer
let vaultOwner: Signer
let router: Contract
let geyser: Contract
let factory: Contract
let stakingToken: Contract
let vaultAddress: string
let vaultNFTId: BigNumber

beforeEach(async () => {
;[deployer, user, vaultOwner] = await ethers.getSigners()

// Mock ERC20 token
const MockERC20 = await ethers.getContractFactory('MockERC20')
stakingToken = await MockERC20.deploy(await user.getAddress(), ethers.utils.parseEther('1000000'))
await stakingToken.deployed()

// Mock Geyser
const MockGeyser = await ethers.getContractFactory('MockGeyser')
geyser = await MockGeyser.deploy(stakingToken.address)
await geyser.deployed()

// Mock Factory (ERC721-based)
const MockVaultFactory = await ethers.getContractFactory('MockVaultFactory')
factory = await MockVaultFactory.deploy()
await factory.deployed()

// Deploy Router
const GeyserRouter = await ethers.getContractFactory('GeyserRouter')
router = await GeyserRouter.deploy()
await router.deployed()
})

describe('create2Vault', () => {
it('should create a vault using create2', async () => {
const salt = ethers.utils.formatBytes32String('testSalt')

vaultAddress = await router.callStatic.create2Vault(factory.address, salt, await vaultOwner.getAddress())
expect(vaultAddress).to.not.be.undefined

// The factory should have minted an NFT representing the vault to the vault owner
await router.create2Vault(factory.address, salt, await vaultOwner.getAddress())
expect(await factory.balanceOf(await vaultOwner.getAddress())).to.equal(1)
})
})

describe('depositStake', () => {
beforeEach(async () => {
// create a vault
const salt = ethers.utils.formatBytes32String('testSalt2')
vaultAddress = await router.callStatic.create2Vault(factory.address, salt, await vaultOwner.getAddress())
await router.create2Vault(factory.address, salt, await vaultOwner.getAddress())
})

it('should deposit stake tokens from user into vault and call stake on geyser', async () => {
const amount = ethers.utils.parseEther('100')
await stakingToken.connect(user).approve(router.address, amount)

await expect(router.connect(user).depositStake(geyser.address, vaultAddress, amount, '0x'))
.to.emit(geyser, 'Staked')
.withArgs(vaultAddress, amount, '0x')

// Check vault (just ensure tokens moved)
expect(await stakingToken.balanceOf(vaultAddress)).to.equal(amount)
})
})

describe('create2VaultAndStake', () => {
it('should create a vault, transfer ownership, and stake tokens', async () => {
const amount = ethers.utils.parseEther('50')
await stakingToken.connect(user).approve(router.address, amount)
const salt = ethers.utils.formatBytes32String('testSalt3')

const args = [geyser.address, factory.address, await vaultOwner.getAddress(), amount, salt, '0x']

const vaultAddr = await router.connect(user).callStatic.create2VaultAndStake(...args)

await expect(router.connect(user).create2VaultAndStake(...args))
.to.emit(geyser, 'Staked')
.withArgs(vaultAddr, amount, '0x')

// vaultOwner should now own the vault NFT
expect(await factory.ownerOf(1)).to.equal(await vaultOwner.getAddress())
})
})

describe('create2VaultPermitAndStake', () => {
it('should create vault, transfer ownership, permit, and stake', async () => {
const amount = ethers.utils.parseEther('200')
const salt = ethers.utils.formatBytes32String('testSalt4')

// Mock permit data. In practice, you'd generate a signature off-chain.
// Here, we simply assume the mock ERC20 supports permit or is a mock that doesn't revert.
const deadline = Math.floor(Date.now() / 1000) + 3600
const owner = await user.getAddress()
const spender = await user.getAddress()
const value = amount

// Normally you'd produce a real permit signature. For testing we assume mock token doesn't revert.
const v = 27
const r = ethers.utils.formatBytes32String('r')
const s = ethers.utils.formatBytes32String('s')

const permit = {
owner,
spender,
value,
deadline,
v,
r,
s,
}

// user must have tokens
await stakingToken.connect(user).approve(router.address, amount)

const args = [geyser.address, factory.address, await vaultOwner.getAddress(), salt, permit, '0x']
const vaultAddr = await router.connect(user).callStatic.create2VaultPermitAndStake(...args)

await expect(router.connect(user).create2VaultPermitAndStake(...args))
.to.emit(geyser, 'Staked')
.withArgs(vaultAddr, amount, '0x')
expect(await factory.ownerOf(1)).to.equal(await vaultOwner.getAddress())
})
})

describe('stakeMulti', () => {
it('should call stake on multiple geysers', async () => {
// create vaults for multiple requests
const salt1 = ethers.utils.formatBytes32String('multi1')
const salt2 = ethers.utils.formatBytes32String('multi2')
const vault1 = await router.callStatic.create2Vault(factory.address, salt1, await vaultOwner.getAddress())
const vault2 = await router.callStatic.create2Vault(factory.address, salt2, await vaultOwner.getAddress())
await router.create2Vault(factory.address, salt1, await vaultOwner.getAddress())
await router.create2Vault(factory.address, salt2, await vaultOwner.getAddress())

const amount1 = ethers.utils.parseEther('10')
const amount2 = ethers.utils.parseEther('20')

const requests = [
{
geyser: geyser.address,
vault: vault1,
amount: amount1,
permission: '0x',
},
{
geyser: geyser.address,
vault: vault2,
amount: amount2,
permission: '0x',
},
]

// In a real scenario, you'd need to ensure the vaults have tokens staked beforehand or
// the geyser allows calling `stake` from the router address. For simplicity, we rely on mock logic.

await expect(router.stakeMulti(requests))
.to.emit(geyser, 'Staked')
.withArgs(vault1, amount1, '0x')
.and.to.emit(geyser, 'Staked')
.withArgs(vault2, amount2, '0x')
})
})

describe('unstakeMulti', () => {
it('should call unstakeAndClaim on multiple geysers', async () => {
const salt = ethers.utils.formatBytes32String('unstakeTest')
const vault = await router.callStatic.create2Vault(factory.address, salt, await vaultOwner.getAddress())
await router.create2Vault(factory.address, salt, await vaultOwner.getAddress())

const amount = ethers.utils.parseEther('5')
const requests = [
{
geyser: geyser.address,
vault: vault,
amount: amount,
permission: '0x',
},
]

await expect(router.unstakeMulti(requests)).to.emit(geyser, 'UnstakedAndClaimed').withArgs(vault, amount, '0x')
})
})

describe('unstakeAndRestake', () => {
it('should call stake on first geyser request and unstakeAndClaim on second geyser request', async () => {
const salt1 = ethers.utils.formatBytes32String('r1')
const salt2 = ethers.utils.formatBytes32String('r2')
const vault1 = await router.callStatic.create2Vault(factory.address, salt1, await vaultOwner.getAddress())
const vault2 = await router.callStatic.create2Vault(factory.address, salt2, await vaultOwner.getAddress())
await router.create2Vault(factory.address, salt1, await vaultOwner.getAddress())
await router.create2Vault(factory.address, salt2, await vaultOwner.getAddress())

const amount1 = ethers.utils.parseEther('15')
const amount2 = ethers.utils.parseEther('25')

const r1 = {
geyser: geyser.address,
vault: vault1,
amount: amount1,
permission: '0x',
}
const r2 = {
geyser: geyser.address,
vault: vault2,
amount: amount2,
permission: '0x',
}

await expect(router.unstakeAndRestake(r1, r2))
.to.emit(geyser, 'Staked')
.withArgs(vault1, amount1, '0x')
.and.to.emit(geyser, 'UnstakedAndClaimed')
.withArgs(vault2, amount2, '0x')
})
})
})

0 comments on commit 905e3af

Please sign in to comment.