Skip to content

Commit

Permalink
Add MigrationZapper
Browse files Browse the repository at this point in the history
  • Loading branch information
shahthepro committed May 28, 2024
1 parent 987fd45 commit 5565133
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 11 deletions.
64 changes: 64 additions & 0 deletions contracts/MigrationZapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

import {IStaking} from "./interfaces/IStaking.sol";
import {IMigrator} from "./interfaces/IMigrator.sol";
import {IMintableERC20} from "./interfaces/IMintableERC20.sol";

contract MigrationZapper {
IMintableERC20 public immutable ogv;
IMintableERC20 public immutable ogn;

IMigrator public immutable migrator;
IStaking public immutable ognStaking;

constructor(address _ogv, address _ogn, address _migrator, address _ognStaking) {
ogv = IMintableERC20(_ogv);
ogn = IMintableERC20(_ogn);
migrator = IMigrator(_migrator);
ognStaking = IStaking(_ognStaking);
}

function initialize() external {
// Migrator can move OGV and OGN from this contract
ogv.approve(address(migrator), type(uint256).max);
ogn.approve(address(ognStaking), type(uint256).max);
}

/**
* @notice Migrates the specified amount of OGV to OGN.
* @param ogvAmount Amount of OGV to migrate
*/
function migrate(uint256 ogvAmount) external {
// Take tokens in
ogv.transferFrom(msg.sender, address(this), ogvAmount);

// Proxy migrate call
uint256 ognReceived = migrator.migrate(ogvAmount);

// Transfer OGN to the receiver
ogn.transfer(msg.sender, ognReceived);
}

/**
* @notice Migrates the specified amount of OGV to OGN
* and stakes it.
* @param ogvAmount Amount of OGV to migrate
*/
function migrate(uint256 ogvAmount, uint256 newStakeAmount, uint256 newStakeDuration) external {
// Take tokens in
ogv.transferFrom(msg.sender, address(this), ogvAmount);

// Migrate
uint256 ognReceived = migrator.migrate(ogvAmount);

// Stake on behalf of user
ognStaking.stake(
newStakeAmount,
newStakeDuration,
msg.sender,
false,
-1 // New stake
);
}
}
10 changes: 1 addition & 9 deletions contracts/Migrator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,7 @@ pragma solidity 0.8.10;
import "OpenZeppelin/openzeppelin-contracts@4.6.0/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "./Governable.sol";

interface IStaking {
function delegates(address staker) external view returns (address);

// From OGVStaking.sol
function unstakeFrom(address staker, uint256[] memory lockupIds) external returns (uint256, uint256);

// From ExponentialStaking.sol
function stake(uint256 amountIn, uint256 duration, address to, bool stakeRewards, int256 lockupId) external;
}
import {IStaking} from "./interfaces/IStaking.sol";

contract Migrator is Governable {
ERC20Burnable public immutable ogv;
Expand Down
15 changes: 15 additions & 0 deletions contracts/interfaces/IMigrator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

interface IMigrator {
function migrate(uint256 ogvAmount) external returns (uint256);

function migrate(
uint256[] calldata lockupIds,
uint256 ogvAmountFromWallet,
uint256 ognAmountFromWallet,
bool migrateRewards,
uint256 newStakeAmount,
uint256 newStakeDuration
) external;
}
1 change: 1 addition & 0 deletions contracts/interfaces/IMintableERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ interface IMintableERC20 {
function balanceOf(address owner) external view returns (uint256);
function totalSupply() external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function transferFrom(address _from, address to, uint256 amount) external returns (bool);
function approve(address spender, uint256 allowance) external;
}
12 changes: 12 additions & 0 deletions contracts/interfaces/IStaking.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

interface IStaking {
function delegates(address staker) external view returns (address);

// From OGVStaking.sol
function unstakeFrom(address staker, uint256[] memory lockupIds) external returns (uint256, uint256);

// From ExponentialStaking.sol
function stake(uint256 amountIn, uint256 duration, address to, bool stakeRewards, int256 lockupId) external;
}
4 changes: 3 additions & 1 deletion script/deploy/DeployManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {BaseMainnetScript} from "./mainnet/BaseMainnetScript.sol";

import {XOGNSetupScript} from "./mainnet/010_xOGNSetupScript.sol";
import {OgnOgvMigrationScript} from "./mainnet/011_OgnOgvMigrationScript.sol";
import {XOGNGovernanceScript} from "./mainnet/012_xOGNGovernanceScript.sol";
import {MigrationZapperScript} from "./mainnet/012_MigrationZapperScript.sol";
import {XOGNGovernanceScript} from "./mainnet/013_xOGNGovernanceScript.sol";

import "contracts/utils/VmHelper.sol";

Expand Down Expand Up @@ -62,6 +63,7 @@ contract DeployManager is Script {
// TODO: Use vm.readDir to recursively build this?
_runDeployFile(new XOGNSetupScript());
_runDeployFile(new OgnOgvMigrationScript());
_runDeployFile(new MigrationZapperScript());
_runDeployFile(new XOGNGovernanceScript());
}

Expand Down
47 changes: 47 additions & 0 deletions script/deploy/mainnet/012_MigrationZapperScript.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.10;

import "./BaseMainnetScript.sol";
import {Vm} from "forge-std/Vm.sol";

import {Addresses} from "contracts/utils/Addresses.sol";

import {Timelock} from "contracts/Timelock.sol";
import {Governance} from "contracts/Governance.sol";

import {GovFive} from "contracts/utils/GovFive.sol";

import {VmHelper} from "utils/VmHelper.sol";

import {MigrationZapper} from "contracts/MigrationZapper.sol";

import "OpenZeppelin/openzeppelin-contracts@4.6.0/contracts/token/ERC20/extensions/ERC20Votes.sol";
import "OpenZeppelin/openzeppelin-contracts@4.6.0/contracts/governance/TimelockController.sol";

contract MigrationZapperScript is BaseMainnetScript {
using GovFive for GovFive.GovFiveProposal;
using VmHelper for Vm;

GovFive.GovFiveProposal public govProposal;

string public constant override DEPLOY_NAME = "012_MigrationZapper";

constructor() {}

function _execute() internal override {
console.log("Deploy:");
console.log("------------");

MigrationZapper zapper =
new MigrationZapper(Addresses.OGV, Addresses.OGN, deployedContracts["MIGRATOR"], deployedContracts["XOGN"]);
_recordDeploy("MIGRATION_ZAPPER", address(zapper));

// Make sure Migrator can move OGV and OGN
zapper.initialize();
}

function _buildGovernanceProposal() internal override {}

function _fork() internal override {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ contract XOGNGovernanceScript is BaseMainnetScript {

GovFive.GovFiveProposal public govProposal;

string public constant override DEPLOY_NAME = "012_xOGNGovernance";
string public constant override DEPLOY_NAME = "013_xOGNGovernance";

uint256 public constant OGN_EPOCH = 1717041600; // May 30, 2024 GMT

Expand Down
87 changes: 87 additions & 0 deletions tests/staking/ZapperForkTest.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.10;

import "forge-std/Test.sol";

import {Addresses} from "contracts/utils/Addresses.sol";
import {DeployManager} from "script/deploy/DeployManager.sol";

import {Migrator} from "contracts/Migrator.sol";
import {MigrationZapper} from "contracts/MigrationZapper.sol";
import {OgvStaking} from "contracts/OgvStaking.sol";
import {ExponentialStaking} from "contracts/ExponentialStaking.sol";

import {IMintableERC20} from "contracts/interfaces/IMintableERC20.sol";

contract ZapperForkTest is Test {
DeployManager public deployManager;

Migrator public migrator;
MigrationZapper public zapper;
OgvStaking public veogv;
ExponentialStaking public xogn;
IMintableERC20 public ogv;
IMintableERC20 public ogn;

address public ogvWhale = Addresses.GOV_MULTISIG;

constructor() {
deployManager = new DeployManager();

deployManager.setUp();
deployManager.run();
}

function setUp() external {
migrator = Migrator(deployManager.getDeployment("MIGRATOR"));
zapper = MigrationZapper(deployManager.getDeployment("MIGRATION_ZAPPER"));

veogv = OgvStaking(Addresses.VEOGV);
ogv = IMintableERC20(Addresses.OGV);
ogn = IMintableERC20(Addresses.OGN);
xogn = ExponentialStaking(deployManager.getDeployment("XOGN"));

vm.startPrank(ogvWhale);
ogv.approve(address(migrator), type(uint256).max);
ogv.approve(address(zapper), type(uint256).max);
ogn.approve(address(migrator), type(uint256).max);
ogv.approve(address(veogv), type(uint256).max);
vm.stopPrank();
}

function testBalanceMigration() external {
vm.startPrank(ogvWhale);

uint256 migratorOGNReserve = ogn.balanceOf(address(migrator));
uint256 ogvSupply = ogv.totalSupply();
uint256 ogvBalanceBefore = ogv.balanceOf(ogvWhale);
uint256 ognBalanceBefore = ogn.balanceOf(ogvWhale);

// Should be able to swap OGV to OGN at fixed rate
zapper.migrate(100 ether);

assertEq(ogv.balanceOf(ogvWhale), ogvBalanceBefore - 100 ether, "More OGV burnt");
assertEq(ogv.totalSupply(), ogvSupply - 100 ether, "OGV supply mismatch");

assertEq(ogn.balanceOf(ogvWhale), ognBalanceBefore + 9.137 ether, "Less OGN received");
assertEq(ogn.balanceOf(address(migrator)), migratorOGNReserve - 9.137 ether, "More OGN sent");

vm.stopPrank();
}

function testMigrateBalanceAndStake() public {
vm.startPrank(ogvWhale);

uint256[] memory lockupIds = new uint256[](0);
uint256 stakeAmount = (100 ether * 0.09137 ether) / 1 ether;

zapper.migrate(100 ether, 1 ether, 300 days);

// Should have it in a single OGN lockup
(uint128 amount, uint128 end, uint256 points) = xogn.lockups(ogvWhale, 0);
assertEq(amount, stakeAmount, "Balance not staked");

vm.stopPrank();
}
}

0 comments on commit 5565133

Please sign in to comment.