Skip to content

Commit

Permalink
feat: Add voting for staked apecoin (#21)
Browse files Browse the repository at this point in the history
* feat: Add voting for staked apecoin

* chore: Deploy StakedVoting on Goerli

* chore: Deploy voting contract
  • Loading branch information
thorseldon authored Nov 21, 2023
1 parent c3149b5 commit 56799e7
Show file tree
Hide file tree
Showing 8 changed files with 312 additions and 0 deletions.
161 changes: 161 additions & 0 deletions abis/BendApeCoinStakedVoting.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
[
{
"inputs": [
{
"internalType": "contract ICoinPool",
"name": "coinPool_",
"type": "address"
},
{
"internalType": "contract INftPool",
"name": "nftPool_",
"type": "address"
},
{
"internalType": "contract IStakeManager",
"name": "staker_",
"type": "address"
},
{
"internalType": "contract IBNFTRegistry",
"name": "bnftRegistry_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "bnftRegistry",
"outputs": [
{
"internalType": "contract IBNFTRegistry",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "coinPool",
"outputs": [
{
"internalType": "contract ICoinPool",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "userAddress",
"type": "address"
}
],
"name": "getVotes",
"outputs": [
{
"internalType": "uint256",
"name": "votes",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "userAddress",
"type": "address"
}
],
"name": "getVotesInAllNftPool",
"outputs": [
{
"internalType": "uint256",
"name": "votes",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "userAddress",
"type": "address"
}
],
"name": "getVotesInCoinPool",
"outputs": [
{
"internalType": "uint256",
"name": "votes",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract IStakedNft",
"name": "stnft_",
"type": "address"
},
{
"internalType": "address",
"name": "userAddress",
"type": "address"
}
],
"name": "getVotesInOneNftPool",
"outputs": [
{
"internalType": "uint256",
"name": "votes",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "nftPool",
"outputs": [
{
"internalType": "contract INftPool",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "staker",
"outputs": [
{
"internalType": "contract IStakeManager",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]
95 changes: 95 additions & 0 deletions contracts/misc/BendApeCoinStakedVoting.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.18;

import {IERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol";

import {IBNFTRegistry} from "../interfaces/IBNFTRegistry.sol";

import {ICoinPool} from "../interfaces/ICoinPool.sol";
import {INftPool, IStakedNft} from "../interfaces/INftPool.sol";
import {IStakeManager} from "../interfaces/IStakeManager.sol";

/**
* @title BendDAO Staked ApeCoin's Voting Contract
* @notice Provides a comprehensive vote count across all pools in the ApeCoinStaking contract
*/
contract BendApeCoinStakedVoting {
ICoinPool public immutable coinPool;
INftPool public immutable nftPool;
IStakeManager public immutable staker;
IBNFTRegistry public immutable bnftRegistry;

constructor(ICoinPool coinPool_, INftPool nftPool_, IStakeManager staker_, IBNFTRegistry bnftRegistry_) {
coinPool = coinPool_;
nftPool = nftPool_;
staker = staker_;
bnftRegistry = bnftRegistry_;
}

/**
* @notice Returns a vote count across all pools in the ApeCoinStaking contract for a given address
* @param userAddress The address to return votes for
*/
function getVotes(address userAddress) public view returns (uint256 votes) {
votes += getVotesInCoinPool(userAddress);
votes += getVotesInAllNftPool(userAddress);
}

function getVotesInCoinPool(address userAddress) public view returns (uint256 votes) {
votes = coinPool.assetBalanceOf(userAddress);
}

function getVotesInAllNftPool(address userAddress) public view returns (uint256 votes) {
votes += getVotesInOneNftPool(staker.stBayc(), userAddress);
votes += getVotesInOneNftPool(staker.stMayc(), userAddress);
votes += getVotesInOneNftPool(staker.stBakc(), userAddress);
}

function getVotesInOneNftPool(IStakedNft stnft_, address userAddress) public view returns (uint256 votes) {
// Check user balance
uint256 stnftBalance = stnft_.balanceOf(userAddress);
uint256 bnftBalance;
(address bnftProxy, ) = bnftRegistry.getBNFTAddresses(address(stnft_));
if (bnftProxy != address(0)) {
bnftBalance += IERC721Enumerable(bnftProxy).balanceOf(userAddress);
}
if (bnftBalance == 0 && stnftBalance == 0) {
return 0;
}

// Get all tokenIds
uint256[] memory allTokenIds = new uint256[](stnftBalance + bnftBalance);
uint256 allIdSize = 0;

for (uint256 i = 0; i < stnftBalance; i++) {
uint256 tokenId = stnft_.tokenOfOwnerByIndex(userAddress, i);
if (stnft_.stakerOf(tokenId) == address(staker)) {
allTokenIds[allIdSize] = tokenId;
allIdSize++;
}
}

if (bnftProxy != address(0)) {
IERC721Enumerable bnft = IERC721Enumerable(bnftProxy);
for (uint256 i = 0; i < bnftBalance; i++) {
uint256 tokenId = bnft.tokenOfOwnerByIndex(userAddress, i);
if (stnft_.stakerOf(tokenId) == address(staker)) {
allTokenIds[allIdSize] = tokenId;
allIdSize++;
}
}
}

// Get votes from claimable rewards
address[] memory claimNfts = new address[](1);
claimNfts[0] = stnft_.underlyingAsset();

uint256[][] memory claimTokenIds = new uint256[][](1);
claimTokenIds[0] = new uint256[](allIdSize);
for (uint256 i = 0; i < allIdSize; i++) {
claimTokenIds[0][i] = allTokenIds[i];
}

votes = nftPool.claimable(claimNfts, claimTokenIds);
}
}
4 changes: 4 additions & 0 deletions deployments/deployed-contracts-goerli.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,9 @@
"LendingMigrator": {
"address": "0x9a922352fF520Ec08B41281B2d1a1a3cfCdA9110",
"deployer": "0x99AcDAB13365e4CFcAa139A31de7CF8C80Ac5fb1"
},
"BendApeCoinStakedVoting": {
"address": "0x463a79b1339BfD188D09CA941D2c8120BdE8A0De",
"deployer": "0xafF5C36642385b6c7Aaf7585eC785aB2316b5db6"
}
}
4 changes: 4 additions & 0 deletions deployments/deployed-contracts-mainnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,9 @@
"LendingMigrator": {
"address": "0x968c9090a217786f414025ceF11540f801f38ee1",
"deployer": "0x868964fa49a6fd6e116FE82c8f4165904406f479"
},
"BendApeCoinStakedVoting": {
"address": "0xd97420142211c0C8ACF6276e73d8B7A7b09386BB",
"deployer": "0x868964fa49a6fd6e116FE82c8f4165904406f479"
}
}
12 changes: 12 additions & 0 deletions tasks/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,18 @@ task("deploy:PoolViewer", "Deploy PoolViewer").setAction(async (_, { network, ru
await deployContract("PoolViewer", [apeStaking, coinPool, stakeManager, bnftRegistry], true);
});

task("deploy:StakedVoting", "Deploy Voting").setAction(async (_, { network, run }) => {
await run("set-DRE");
await run("compile");

const coinPool = await getContractAddressFromDB("BendCoinPool");
const nftPool = await getContractAddressFromDB("BendNftPool");
const stakeManager = await getContractAddressFromDB("BendStakeManager");
const bnftRegistry = getParams(BNFT_REGISTRY, network.name);

await deployContract("BendApeCoinStakedVoting", [coinPool, nftPool, stakeManager, bnftRegistry], true);
});

task("deploy:config:BendCoinPool", "Coinfig BendCoinPool").setAction(async (_, { network, run }) => {
await run("set-DRE");
await run("compile");
Expand Down
6 changes: 6 additions & 0 deletions test/hardhat/BendCoinPool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ makeSuite("BendCoinPool", (contracts: Contracts, env: Env, snapshots: Snapshots)
await snapshots.capture(lastRevert);
});

it("getVotes", async () => {
const assetsAmount = await contracts.bendCoinPool.assetBalanceOf(bob.address);
const votesAmount = await contracts.stakedVoting.getVotes(bob.address);
expect(assetsAmount).eq(votesAmount);
});

it("withdraw: revert when paused", async () => {
const withdrawAmount = await contracts.bendCoinPool.assetBalanceOf(bob.address);
await contracts.bendCoinPool.setPause(true);
Expand Down
18 changes: 18 additions & 0 deletions test/hardhat/BendNftPool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ makeSuite("BendNftPool", (contracts: Contracts, env: Env, snapshots: Snapshots)
await expectClaimable(contracts.bayc.address, tokenIds);
});

it("getVotes: bayc", async () => {
const rewardsAmount = await contracts.bendNftPool.claimable([contracts.bayc.address], [baycTokenIds]);
const votesAmount = await contracts.stakedVoting.getVotesInOneNftPool(contracts.stBayc.address, owner.address);
expect(rewardsAmount).eq(votesAmount);
});

it("deposit: deposit mayc", async () => {
for (const id of maycTokenIds) {
await expect(contracts.stMayc.ownerOf(id)).revertedWith("ERC721: invalid token ID");
Expand Down Expand Up @@ -181,6 +187,12 @@ makeSuite("BendNftPool", (contracts: Contracts, env: Env, snapshots: Snapshots)
await expectClaimable(contracts.mayc.address, tokenIds);
});

it("getVotes: mayc", async () => {
const rewardsAmount = await contracts.bendNftPool.claimable([contracts.mayc.address], [maycTokenIds]);
const votesAmount = await contracts.stakedVoting.getVotesInOneNftPool(contracts.stMayc.address, owner.address);
expect(rewardsAmount).eq(votesAmount);
});

it("deposit: deposit bakc", async () => {
for (const id of bakcTokenIds) {
await expect(contracts.stBakc.ownerOf(id)).revertedWith("ERC721: invalid token ID");
Expand Down Expand Up @@ -209,6 +221,12 @@ makeSuite("BendNftPool", (contracts: Contracts, env: Env, snapshots: Snapshots)
await expectClaimable(contracts.bakc.address, tokenIds);
});

it("getVotes: bakc", async () => {
const rewardsAmount = await contracts.bendNftPool.claimable([contracts.bakc.address], [bakcTokenIds]);
const votesAmount = await contracts.stakedVoting.getVotesInOneNftPool(contracts.stBakc.address, owner.address);
expect(rewardsAmount).eq(votesAmount);
});

const expectClaim = async (nft: string, tokenIds: number[]) => {
const claimable = await contracts.bendNftPool.claimable([nft], [tokenIds]);
const pendingApeCoin = (await contracts.bendNftPool.getPoolStateUI(nft)).pendingApeCoin;
Expand Down
12 changes: 12 additions & 0 deletions test/hardhat/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
MockStakeManagerV1,
CompoudV1Migrator,
PoolViewer,
BendApeCoinStakedVoting,
} from "../../typechain-types";
import { Contract, BigNumber, constants } from "ethers";
import { parseEther } from "ethers/lib/utils";
Expand Down Expand Up @@ -85,6 +86,8 @@ export interface Contracts {
mockStakeManagerV1: MockStakeManagerV1;
compoudV1Migrator: CompoudV1Migrator;
poolViewer: PoolViewer;
// voting
stakedVoting: BendApeCoinStakedVoting;
}

export async function setupEnv(env: Env, contracts: Contracts): Promise<void> {
Expand Down Expand Up @@ -342,6 +345,14 @@ export async function setupContracts(): Promise<Contracts> {
bnftRegistry.address,
]);

// voting
const stakedVoting = await deployContract<BendApeCoinStakedVoting>("BendApeCoinStakedVoting", [
bendCoinPool.address,
bendNftPool.address,
bendStakeManager.address,
bnftRegistry.address,
]);

return {
initialized: true,
delegateCash,
Expand Down Expand Up @@ -377,6 +388,7 @@ export async function setupContracts(): Promise<Contracts> {
mockStakeManagerV1,
compoudV1Migrator,
poolViewer,
stakedVoting,
} as Contracts;
}

Expand Down

0 comments on commit 56799e7

Please sign in to comment.