Skip to content

Commit

Permalink
[Polygon] feat: mainnet fork tests
Browse files Browse the repository at this point in the history
  • Loading branch information
kyriediculous committed Dec 14, 2023
1 parent f93412e commit d9816a6
Show file tree
Hide file tree
Showing 6 changed files with 312 additions and 40 deletions.
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ depth = 100
# Uncomment to enable the RPC server
arbitrum_goerli = "${ARBITRUM_GOERLI_RPC}"
arbitrum = "${ARBITRUM_RPC}"
mainnet = "${MAINNET_RPC}"
54 changes: 32 additions & 22 deletions src/adapters/PolygonAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ import { ERC20 } from "solmate/tokens/ERC20.sol";
import { Adapter } from "core/adapters/Adapter.sol";
import { IERC165 } from "core/interfaces/IERC165.sol";
import { ITenderizer } from "core/tenderizer/ITenderizer.sol";
import { IMaticStakeManager, IValidatorShares, DelegatorUnbond } from "core/adapters/interfaces/IPolygon.sol";
import { IPolygonStakeManager, IPolygonValidatorShares, DelegatorUnbond } from "core/adapters/interfaces/IPolygon.sol";

// Matic exchange rate precision
uint256 constant EXCHANGE_RATE_PRECISION = 100; // For Validator ID < 8
uint256 constant EXCHANGE_RATE_PRECISION_HIGH = 10 ** 29; // For Validator ID >= 8
uint256 constant WITHDRAW_DELAY = 80; // 80 epochs, epoch length can vary on average between 200-300 Ethereum L1 blocks

IPolygonStakeManager constant POLYGON_STAKEMANAGER = IPolygonStakeManager(address(0x5e3Ef299fDDf15eAa0432E6e66473ace8c13D908));
ERC20 constant POL = ERC20(address(0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0));

// Polygon validators with a `validatorId` less than 8 are foundation validators
// These are special case validators that don't have slashing enabled and still operate
// On the old precision for the ValidatorShares contract.
Expand All @@ -37,29 +40,36 @@ function getExchangePrecision(uint256 validatorId) pure returns (uint256) {
contract PolygonAdapter is Adapter {
using SafeTransferLib for ERC20;

IMaticStakeManager private constant MATIC_STAKE_MANAGER =
IMaticStakeManager(address(0x5e3Ef299fDDf15eAa0432E6e66473ace8c13D908));
ERC20 private constant POLY = ERC20(address(0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0));

function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
return interfaceId == type(Adapter).interfaceId || interfaceId == type(IERC165).interfaceId;
}

function isValidator(address validator) public view returns (bool) {
// Validator must have a validator shares contract
// This will revert if the address does not own its StakeNFT
// Which could lead to unexpected behaviour if used by external contracts
return address(_getValidatorSharesContract(_getValidatorId(validator))) != address(0);
}

function previewDeposit(address, /*validator*/ uint256 assets) external pure returns (uint256) {
return assets;
function previewDeposit(address validator, uint256 assets) external view returns (uint256) {
uint256 validatorId = _getValidatorId(validator);
uint256 delegatedAmount = IPolygonStakeManager(POLYGON_STAKEMANAGER).delegatedAmount(validatorId);
IPolygonValidatorShares validatorShares = _getValidatorSharesContract(validatorId);
uint256 totalShares = validatorShares.totalSupply();
uint256 prec = getExchangePrecision(_getValidatorId(validator));
uint256 fxRate_0 = prec * delegatedAmount / totalShares;
uint256 sharesToMint = assets * prec / fxRate_0;
uint256 amountToTransfer = sharesToMint * fxRate_0 / prec;
uint256 fxRate_1 = prec * (delegatedAmount + amountToTransfer) / (totalShares + sharesToMint);
return sharesToMint * fxRate_1 / prec;
}

function previewWithdraw(uint256 unlockID) external view returns (uint256 amount) {
// get validator for caller (Tenderizer through delegate call)
address validator = _getValidatorAddress();
// get the validator shares contract for validator
uint256 validatorId = _getValidatorId(validator);
IValidatorShares validatorShares = _getValidatorSharesContract(validatorId);
IPolygonValidatorShares validatorShares = _getValidatorSharesContract(validatorId);

DelegatorUnbond memory unbond = validatorShares.unbonds_new(address(this), unlockID);
// calculate amount of tokens to withdraw by converting shares back into amount
Expand All @@ -82,15 +92,15 @@ contract PolygonAdapter is Adapter {
}

function currentTime() external view override returns (uint256) {
return MATIC_STAKE_MANAGER.epoch();
return POLYGON_STAKEMANAGER.epoch();
}

function stake(address validator, uint256 amount) external override returns (uint256) {
// approve tokens
POLY.safeApprove(address(MATIC_STAKE_MANAGER), amount);
POL.safeApprove(address(POLYGON_STAKEMANAGER), amount);

uint256 validatorId = _getValidatorId(validator);
IValidatorShares validatorShares = _getValidatorSharesContract(validatorId);
IPolygonValidatorShares validatorShares = _getValidatorSharesContract(validatorId);

// calculate minimum amount of voucher shares to mint
// adjust for integer truncation upon division
Expand All @@ -99,13 +109,12 @@ contract PolygonAdapter is Adapter {
uint256 min = amount * precision / fxRate - 1;

// Mint voucher shares
validatorShares.buyVoucher(amount, min);
return amount;
return validatorShares.buyVoucher(amount, min);
}

function unstake(address validator, uint256 amount) external override returns (uint256 unlockID) {
uint256 validatorId = _getValidatorId(validator);
IValidatorShares validatorShares = _getValidatorSharesContract(validatorId);
IPolygonValidatorShares validatorShares = _getValidatorSharesContract(validatorId);

uint256 precision = getExchangePrecision(validatorId);
uint256 fxRate = validatorShares.exchangeRate();
Expand All @@ -120,7 +129,7 @@ contract PolygonAdapter is Adapter {

function withdraw(address validator, uint256 unlockID) external override returns (uint256 amount) {
uint256 validatorId = _getValidatorId(validator);
IValidatorShares validatorShares = _getValidatorSharesContract(validatorId);
IPolygonValidatorShares validatorShares = _getValidatorSharesContract(validatorId);

DelegatorUnbond memory unbond = validatorShares.unbonds_new(address(this), unlockID);
// foundation validators (id < 8) don't have slashing enabled
Expand All @@ -133,7 +142,7 @@ contract PolygonAdapter is Adapter {

function rebase(address validator, uint256 currentStake) external returns (uint256 newStake) {
uint256 validatorId = _getValidatorId(validator);
IValidatorShares validatorShares = _getValidatorSharesContract(validatorId);
IPolygonValidatorShares validatorShares = _getValidatorSharesContract(validatorId);

// This call will revert if there are no rewards
// In which case we don't throw, just return the current staked amount.
Expand All @@ -153,12 +162,13 @@ contract PolygonAdapter is Adapter {
function _getValidatorAddress() internal view returns (address) {
return ITenderizer(address(this)).validator();
}
}

function _getValidatorId(address validator) internal view returns (uint256) {
return MATIC_STAKE_MANAGER.getValidatorId(validator);
}
function _getValidatorId(address validator) view returns (uint256) {
// This will revert if validator is not valid
return POLYGON_STAKEMANAGER.getValidatorId(validator);
}

function _getValidatorSharesContract(uint256 validatorId) internal view returns (IValidatorShares) {
return IValidatorShares(MATIC_STAKE_MANAGER.getValidatorContract(validatorId));
}
function _getValidatorSharesContract(uint256 validatorId) view returns (IPolygonValidatorShares) {
return IPolygonValidatorShares(POLYGON_STAKEMANAGER.getValidatorContract(validatorId));
}
9 changes: 6 additions & 3 deletions src/adapters/interfaces/IPolygon.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,19 @@ struct DelegatorUnbond {
uint256 withdrawEpoch;
}

interface IMaticStakeManager {
interface IPolygonStakeManager {
function getValidatorId(address user) external view returns (uint256);
function getValidatorContract(uint256 validatorId) external view returns (address);
function epoch() external view returns (uint256);
function delegatedAmount(uint256 validatorId) external view returns (uint256);
}

interface IValidatorShares {
interface IPolygonValidatorShares {
function owner() external view returns (address);

function restake() external;

function buyVoucher(uint256 _amount, uint256 _minSharesToMint) external;
function buyVoucher(uint256 _amount, uint256 _minSharesToMint) external returns (uint256 amount);

function sellVoucher_new(uint256 claimAmount, uint256 maximumSharesToBurn) external;

Expand All @@ -39,4 +40,6 @@ interface IValidatorShares {
function withdrawExchangeRate() external view returns (uint256);

function unbonds_new(address, uint256) external view returns (DelegatorUnbond memory);

function totalSupply() external view returns (uint256);
}
28 changes: 18 additions & 10 deletions test/adapters/PolygonAdapter.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pragma solidity >=0.8.19;
import { Test, stdError } from "forge-std/Test.sol";
import { PolygonAdapter, EXCHANGE_RATE_PRECISION_HIGH, WITHDRAW_DELAY } from "core/adapters/PolygonAdapter.sol";
import { ITenderizer } from "core/tenderizer/ITenderizer.sol";
import { IMaticStakeManager, IValidatorShares, DelegatorUnbond } from "core/adapters/interfaces/IPolygon.sol";
import { IPolygonStakeManager, IPolygonValidatorShares, DelegatorUnbond } from "core/adapters/interfaces/IPolygon.sol";
import { AdapterDelegateCall } from "core/adapters/Adapter.sol";

contract PolygonAdapterTest is Test {
Expand All @@ -38,11 +38,13 @@ contract PolygonAdapterTest is Test {
vm.mockCall(address(this), abi.encodeCall(ITenderizer.validator, ()), abi.encode(address(this)));
// set validator id for `address(this)` to 8 (not a foundation validator)
vm.mockCall(
MATIC_STAKE_MANAGER, abi.encodeCall(IMaticStakeManager.getValidatorId, (address(this))), abi.encode(validatorId)
MATIC_STAKE_MANAGER, abi.encodeCall(IPolygonStakeManager.getValidatorId, (address(this))), abi.encode(validatorId)
);
// set validator shares contract for `address(this)` to `validatorShares`
vm.mockCall(
MATIC_STAKE_MANAGER, abi.encodeCall(IMaticStakeManager.getValidatorContract, (validatorId)), abi.encode(validatorShares)
MATIC_STAKE_MANAGER,
abi.encodeCall(IPolygonStakeManager.getValidatorContract, (validatorId)),
abi.encode(validatorShares)
);
}

Expand All @@ -65,8 +67,10 @@ contract PolygonAdapterTest is Test {

DelegatorUnbond memory unbond = DelegatorUnbond({ shares: shares, withdrawEpoch: 0 });

vm.mockCall(validatorShares, abi.encodeCall(IValidatorShares.unbonds_new, (address(this), unlockID)), abi.encode(unbond));
vm.mockCall(validatorShares, abi.encodeCall(IValidatorShares.withdrawExchangeRate, ()), abi.encode(fxRate));
vm.mockCall(
validatorShares, abi.encodeCall(IPolygonValidatorShares.unbonds_new, (address(this), unlockID)), abi.encode(unbond)
);
vm.mockCall(validatorShares, abi.encodeCall(IPolygonValidatorShares.withdrawExchangeRate, ()), abi.encode(fxRate));
uint256 actual = abi.decode(adapter._delegatecall(abi.encodeCall(PolygonAdapter.previewWithdraw, (unlockID))), (uint256));
assertEq(actual, expected);
}
Expand All @@ -76,22 +80,26 @@ contract PolygonAdapterTest is Test {
uint256 unlockID = 1;
DelegatorUnbond memory unbond = DelegatorUnbond({ shares: 0, withdrawEpoch: epoch });

vm.mockCall(validatorShares, abi.encodeCall(IValidatorShares.unbonds_new, (address(this), unlockID)), abi.encode(unbond));
vm.mockCall(
validatorShares, abi.encodeCall(IPolygonValidatorShares.unbonds_new, (address(this), unlockID)), abi.encode(unbond)
);
uint256 actual = abi.decode(adapter._delegatecall(abi.encodeCall(PolygonAdapter.unlockMaturity, (unlockID))), (uint256));
assertEq(actual, epoch + WITHDRAW_DELAY);
}

function test_rebase() public {
uint256 currentStake = 100;
uint256 newStake = 200;
vm.mockCall(validatorShares, abi.encodeCall(IValidatorShares.exchangeRate, ()), abi.encode(EXCHANGE_RATE_PRECISION_HIGH));
vm.mockCall(validatorShares, abi.encodeCall(IValidatorShares.balanceOf, (address(this))), abi.encode(newStake));
vm.mockCallRevert(validatorShares, abi.encodeCall(IValidatorShares.restake, ()), "");
vm.mockCall(
validatorShares, abi.encodeCall(IPolygonValidatorShares.exchangeRate, ()), abi.encode(EXCHANGE_RATE_PRECISION_HIGH)
);
vm.mockCall(validatorShares, abi.encodeCall(IPolygonValidatorShares.balanceOf, (address(this))), abi.encode(newStake));
vm.mockCallRevert(validatorShares, abi.encodeCall(IPolygonValidatorShares.restake, ()), "");
uint256 actual =
abi.decode(adapter._delegatecall(abi.encodeCall(PolygonAdapter.rebase, (address(this), currentStake))), (uint256));
assertEq(actual, currentStake);

vm.mockCall(validatorShares, abi.encodeCall(IValidatorShares.restake, ()), abi.encode(true));
vm.mockCall(validatorShares, abi.encodeCall(IPolygonValidatorShares.restake, ()), abi.encode(true));
actual = abi.decode(adapter._delegatecall(abi.encodeCall(PolygonAdapter.rebase, (address(this), currentStake))), (uint256));
assertEq(actual, newStake);
}
Expand Down
10 changes: 5 additions & 5 deletions test/fork-tests/Graph.arbitrum.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

pragma solidity >=0.8.19;

import { Test, console } from "forge-std/Test.sol";
import { Test } from "forge-std/Test.sol";
import { VmSafe } from "forge-std/Vm.sol";
import { MockERC20 } from "solmate/test/utils/mocks/MockERC20.sol";

Expand Down Expand Up @@ -112,10 +112,10 @@ contract GraphForkTest is Test, TenderizerEvents, ERC721Receiver {
}

function test_factory_newTenderizer() public {
// Revert with inactive orchestrator
address inactiveOrchestrator = makeAddr("INACTIVE_ORCHESTRATOR");
vm.expectRevert(abi.encodeWithSelector(Factory.NotValidator.selector, (inactiveOrchestrator)));
fixture.factory.newTenderizer(address(GRT), inactiveOrchestrator);
// Revert with inactive indexer
address inactiveIndexer = makeAddr("INACTIVE_INDEXER");
vm.expectRevert(abi.encodeWithSelector(Factory.NotValidator.selector, (inactiveIndexer)));
fixture.factory.newTenderizer(address(GRT), inactiveIndexer);

// Deploy tenderizer
vm.expectEmit({ checkTopic1: true, checkTopic2: true, checkTopic3: false, checkData: false });
Expand Down
Loading

0 comments on commit d9816a6

Please sign in to comment.