Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: adds ape-rescue helper functions #349

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions contracts/protocol/libraries/helpers/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,5 @@ library Errors {
string public constant NOT_THE_BAKC_OWNER = "130"; //user is not the bakc owner.
string public constant CALLER_NOT_EOA = "131"; //The caller of the function is not an EOA account
string public constant MAKER_SAME_AS_TAKER = "132"; //maker and taker shouldn't be the same address
string public constant CALLER_NOT_ADMIN = "134"; //caller not admin
}
103 changes: 103 additions & 0 deletions contracts/protocol/tokenization/NTokenApeStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import {IERC20} from "../../dependencies/openzeppelin/contracts/IERC20.sol";
import {IERC721} from "../../dependencies/openzeppelin/contracts/IERC721.sol";
import {IRewardController} from "../../interfaces/IRewardController.sol";
import {ApeStakingLogic} from "./libraries/ApeStakingLogic.sol";
import {MintableERC721Logic} from "./libraries/MintableERC721Logic.sol";
import {Errors} from "../libraries/helpers/Errors.sol";

import "../../interfaces/INTokenApeStaking.sol";

/**
Expand All @@ -30,6 +33,20 @@ abstract contract NTokenApeStaking is NToken, INTokenApeStaking {
*/
uint256 internal constant DEFAULT_UNSTAKE_INCENTIVE_PERCENTAGE = 30;

function _onlyApeRescueAdmin() private view {
ApeStakingLogic.APEStakingParameter storage s = apeStakingDataStorage();

require(msg.sender == s.apeRescueAdmin, Errors.CALLER_NOT_ADMIN);
}

/**
* @dev Only pool admin can call functions marked by this modifier.
**/
modifier onlyApeRescueAdmin() {
_onlyApeRescueAdmin();
_;
}

/**
* @dev Constructor.
* @param pool The address of the Pool contract
Expand Down Expand Up @@ -227,4 +244,90 @@ abstract contract NTokenApeStaking is NToken, INTokenApeStaking {
function getBAKCNTokenAddress() internal view returns (address) {
return POOL.getReserveData(address(getBAKC())).xTokenAddress;
}

/**
* @dev Sets a new Ape Rescue Admin
* @param newAdmin The address of the new Ape Rescue Admin
*/
function setApeRescueAdmin(address newAdmin) external onlyPoolAdmin {
require(newAdmin != address(0), Errors.ZERO_ADDRESS_NOT_VALID);

ApeStakingLogic.APEStakingParameter storage s = apeStakingDataStorage();
s.apeRescueAdmin = newAdmin;
}

/**
* @dev Rescues locked APE tokens
* @param amount The amount of APE tokens to rescue
* @param to The address to send the rescued tokens
*/
function rescueLockedAPE(uint256 amount, address to)
external
onlyApeRescueAdmin
{
IERC20 apeCoin = _apeCoinStaking.apeCoin();
MintableERC721Logic.executeRescueERC20(address(apeCoin), to, amount);
}

/**
* @dev Updates the Ape Rescue Claim
* @param tokenId The token ID associated with the claim
* @param txHash The transaction hash of the claim
* @param amount The claim amount
* @param status The claim status
*/
function updateApeRescueClaim(
uint256 tokenId,
bytes32 txHash,
uint128 amount,
ApeStakingLogic.APERescueClaimStatus status
) external onlyApeRescueAdmin {
ApeStakingLogic.APEStakingParameter storage s = apeStakingDataStorage();
ApeStakingLogic.ClaimData memory claimData = s.apeRescueClaims[tokenId][
txHash
];

require(
claimData.status != ApeStakingLogic.APERescueClaimStatus.CLAIMED,
"Already Claimed"
);
require(amount > 0, "amount can't be zero");

s.apeRescueClaims[tokenId][txHash].amount = amount;
s.apeRescueClaims[tokenId][txHash].status = status;
}

/**
* @dev Claims the locked APE tokens
* @param tokenId The token ID associated with the claim
* @param txHash The transaction hash of the claim
*/
function claimLockedAPE(uint256 tokenId, bytes32 txHash) external {
ApeStakingLogic.APEStakingParameter storage s = apeStakingDataStorage();
ApeStakingLogic.ClaimData memory claimData = s.apeRescueClaims[tokenId][
txHash
];

require(
claimData.status == ApeStakingLogic.APERescueClaimStatus.APPROVED,
"Claim not approved"
);
require(
IERC721(_ERC721Data.underlyingAsset).ownerOf(tokenId) ==
msg.sender ||
ownerOf(tokenId) == msg.sender,
Errors.NOT_THE_OWNER
);

s.apeRescueClaims[tokenId][txHash].status = ApeStakingLogic
.APERescueClaimStatus
.CLAIMED;

IERC20 apeCoin = _apeCoinStaking.apeCoin();
MintableERC721Logic.executeRescueERC20(
address(apeCoin),
msg.sender,
claimData.amount
);
}
}
13 changes: 13 additions & 0 deletions contracts/protocol/tokenization/libraries/ApeStakingLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,21 @@ library ApeStakingLogic {
uint256 constant MAYC_POOL_ID = 2;
uint256 constant BAKC_POOL_ID = 3;

enum APERescueClaimStatus {
REVOKED,
APPROVED,
CLAIMED
}

struct ClaimData {
APERescueClaimStatus status;
uint128 amount;
}

struct APEStakingParameter {
uint256 unstakeIncentive;
address apeRescueAdmin;
mapping(uint256 => mapping(bytes32 => ClaimData)) apeRescueClaims;
}
event UnstakeApeIncentiveUpdated(uint256 oldValue, uint256 newValue);

Expand Down
69 changes: 69 additions & 0 deletions test/_pool_ape_staking.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2982,4 +2982,73 @@ describe("APE Coin Staking Test", () => {
.data;
expect(isUsingAsCollateral(configDataAfter, sApeReserveData.id)).true;
});

it("sets a new Ape Rescue Admin", async () => {
const {
users: [user1, , user3],
ape,
mayc,
pool,
nMAYC,
deployer,
} = await loadFixture(fixture);

await expect(
await nMAYC
.connect(deployer.signer)
.setApeRescueAdmin(await user1.address)
);
});

it("reverts when trying to set zero address as Ape Rescue Admin", async () => {
const {
users: [user1, , user3],
ape,
mayc,
pool,
nMAYC,
deployer,
} = await loadFixture(fixture);

await expect(
nMAYC.connect(deployer.signer).setApeRescueAdmin(ZERO_ADDRESS)
).to.be.revertedWith("ZERO_ADDRESS_NOT_VALID");
});

it("rescues locked APE tokens", async () => {
const {
users: [user1, , user3],
ape,
mayc,
pool,
nMAYC,
deployer,
} = await loadFixture(fixture);

const initialBalance = await ape.balanceOf(user3.address);

await ape["mint(address,uint256)"](nMAYC.address, parseEther("110"));
await nMAYC
.connect(user3.signer)
.rescueLockedAPE(parseEther("100"), user3.address);

const finalBalance = await ape.balanceOf(user3.address);
expect(finalBalance).to.equal(initialBalance.add(parseEther("100")));
});

it("reverts when non-apeRescueAdmin tries to rescue locked APE tokens", async () => {
const {
users: [user1, , user3],
ape,
mayc,
pool,
nMAYC,
deployer,
} = await loadFixture(fixture);
await expect(
nMAYC
.connect(user3.signer)
.rescueLockedAPE(parseEther("100"), user3.address)
).to.be.revertedWith("Caller is not the Ape Rescue Admin");
});
});