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

feat: Boolean toggle for migration (SC-16793) #21

Merged
merged 8 commits into from
Jun 10, 2024
Merged
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
20 changes: 18 additions & 2 deletions contracts/Migrator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,26 @@ pragma solidity 0.8.7;

import { ERC20Helper } from "../modules/erc20-helper/src/ERC20Helper.sol";

import { IERC20Like } from "./interfaces/Interfaces.sol";
import { IERC20Like, IGlobalsLike } from "./interfaces/Interfaces.sol";

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

contract Migrator is IMigrator {

address public immutable override globals;
address public immutable override newToken;
address public immutable override oldToken;

uint256 public immutable override tokenSplitScalar;

constructor(address oldToken_, address newToken_, uint256 scalar_) {
bool public override active;

constructor(address globals_, address oldToken_, address newToken_, uint256 scalar_) {
require(scalar_ > 0, "M:C:ZERO_SCALAR");

require(IERC20Like(newToken_).decimals() == IERC20Like(oldToken_).decimals(), "M:C:DECIMAL_MISMATCH");

JGcarv marked this conversation as resolved.
Show resolved Hide resolved
globals = globals_;
oldToken = oldToken_;
newToken = newToken_;

Expand All @@ -29,10 +34,21 @@ contract Migrator is IMigrator {
}

function migrate(address owner_, uint256 amount_) public override {
require(active, "M:M:INACTIVE");
require(amount_ != uint256(0), "M:M:ZERO_AMOUNT");

require(ERC20Helper.transferFrom(oldToken, owner_, address(this), amount_), "M:M:TRANSFER_FROM_FAILED");
require(ERC20Helper.transfer(newToken, owner_, amount_ * tokenSplitScalar), "M:M:TRANSFER_FAILED");
}

function setActive(bool active_) external override {
require(
msg.sender == IGlobalsLike(globals).governor() ||
msg.sender == IGlobalsLike(globals).operationalAdmin(),
"M:SA:NOT_PROTOCOL_ADMIN"
);

active = active_;
}

}
18 changes: 18 additions & 0 deletions contracts/interfaces/IMigrator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@ pragma solidity ^0.8.7;

interface IMigrator {

/**
* @dev Get the status of the migrator.
* @return active_ True if migrations are active.
*/
function active() external view returns (bool active_);

/**
* @dev Gets the Maple Globals address.
* @param globals_ The address of the Maple globals.
*/
function globals() external view returns (address globals_);

/**
* @dev Get address of newToken.
* @return newToken_ The address of new token.
Expand All @@ -28,6 +40,12 @@ interface IMigrator {
*/
function migrate(address owner_, uint256 amount_) external;

/**
* @dev Set the migrator to active or inactive.
* @param active_ True if migrations are active.
*/
function setActive(bool active_) external;

/**
* @dev Get the scalar value for token split.
* @return tokenSplitScalar_ The scalar value for token split.
Expand Down
8 changes: 8 additions & 0 deletions contracts/interfaces/Interfaces.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,11 @@ interface IERC20Like {
function decimals() external view returns (uint8 decimals_);

}

interface IGlobalsLike {

function governor() external view returns (address governor_);

function operationalAdmin() external view returns (address operationalAdmin_);

}
123 changes: 107 additions & 16 deletions tests/Migrator.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,96 @@ import { MockERC20 } from "../modules/erc20/contracts/test/mocks/MockERC20.sol";

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

import { MockGlobals } from "./mocks/Mocks.sol";

contract MigratorConstructorTest is Test {

function test_constructor_zeroScalar() external {
vm.expectRevert("M:C:ZERO_SCALAR");
new Migrator(address(0), address(0), 0);
new Migrator(address(0), address(0), address(0), 0);
}

function test_constructor_mismatch_decimals() external {
MockGlobals globals = new MockGlobals();
MockERC20 oldToken = new MockERC20("Old Token", "OT", 18);
MockERC20 newToken = new MockERC20("New Token", "NT", 17);

vm.expectRevert("M:C:DECIMAL_MISMATCH");
new Migrator(address(oldToken), address(newToken), 1);
new Migrator(address(globals), address(oldToken), address(newToken), 1);
}

function test_constructor() external {
MockERC20 oldToken = new MockERC20("Old Token", "OT", 18);
MockERC20 newToken = new MockERC20("New Token", "NT", 18);
MockGlobals globals = new MockGlobals();
MockERC20 oldToken = new MockERC20("Old Token", "OT", 18);
MockERC20 newToken = new MockERC20("New Token", "NT", 18);

Migrator migrator = new Migrator(address(oldToken), address(newToken), 1);
Migrator migrator = new Migrator(address(globals), address(oldToken), address(newToken), 1);

assertEq(migrator.tokenSplitScalar(), 1);
assertEq(migrator.globals(), address(globals));
assertEq(migrator.oldToken(), address(oldToken));
assertEq(migrator.newToken(), address(newToken));
assertEq(migrator.tokenSplitScalar(), 1);
}

}

contract SetActiveTests is Test {

uint256 internal constant SCALAR = 10;
uint256 internal constant OLD_SUPPLY = 10_000_000e18;

address operationalAdmin = makeAddr("operationalAdmin");
address governor = makeAddr("governor");
address account = makeAddr("account");

Migrator internal _migrator;
MockERC20 internal _oldToken;
MockERC20 internal _newToken;
MockGlobals internal _globals;

function setUp() external {
_globals = new MockGlobals();

_globals.__setGovernor(governor);
_globals.__setOperationalAdmin(operationalAdmin);

_oldToken = new MockERC20("Old Token", "OT", 18);
_newToken = new MockERC20("New Token", "NT", 18);

_migrator = new Migrator(address(_globals), address(_oldToken), address(_newToken), SCALAR);
}

function test_setActive_notProtocolAdmin() external {
vm.expectRevert("M:SA:NOT_PROTOCOL_ADMIN");
_migrator.setActive(false);
}

function test_setActive_withGovernor() external {
assertEq(_migrator.active(), false);

vm.prank(governor);
_migrator.setActive(true);

assertEq(_migrator.active(), true);

vm.prank(governor);
_migrator.setActive(false);

assertEq(_migrator.active(), false);
}

function test_setActive_withOperationalAdmin() external {
assertEq(_migrator.active(), false);

vm.prank(operationalAdmin);
_migrator.setActive(true);

assertEq(_migrator.active(), true);

vm.prank(operationalAdmin);
_migrator.setActive(false);

assertEq(_migrator.active(), false);
}

}
Expand All @@ -40,17 +106,26 @@ contract MigratorTest is Test {
uint256 internal constant SCALAR = 10;
uint256 internal constant OLD_SUPPLY = 10_000_000e18;

address account = makeAddr("account");
address operationalAdmin = makeAddr("operationalAdmin");
address account = makeAddr("account");

Migrator internal _migrator;
MockERC20 internal _oldToken;
MockERC20 internal _newToken;
Migrator internal _migrator;
MockERC20 internal _oldToken;
MockERC20 internal _newToken;
MockGlobals internal _globals;

function setUp() external {
_globals = new MockGlobals();

_oldToken = new MockERC20("Old Token", "OT", 18);
_newToken = new MockERC20("New Token", "NT", 18);

_migrator = new Migrator(address(_oldToken), address(_newToken), SCALAR);
_migrator = new Migrator(address(_globals), address(_oldToken), address(_newToken), SCALAR);

_globals.__setOperationalAdmin(operationalAdmin);

vm.prank(operationalAdmin);
_migrator.setActive(true);

// Mint new token to migrator
_newToken.mint(address(_migrator), OLD_SUPPLY * SCALAR);
Expand All @@ -75,6 +150,14 @@ contract MigratorTest is Test {
assertEq(_newToken.balanceOf(address(_migrator)), (OLD_SUPPLY * SCALAR)- newAmount);
}

function test_migrate_inactive() external {
vm.prank(operationalAdmin);
_migrator.setActive(false);

vm.expectRevert("M:M:INACTIVE");
_migrator.migrate(1);
}

function testFuzz_migrate_insufficientApproval(uint256 amount_) external {
amount_ = bound(amount_, 1, OLD_SUPPLY);

Expand Down Expand Up @@ -259,27 +342,35 @@ contract MigratorTest is Test {

contract TokenSplitScalars is Test {

uint256 internal constant OLD_SUPPLY = 10_000_000 ether;
uint256 internal constant OLD_SUPPLY = 10_000_000e18;

address account = makeAddr("account");
address operationalAdmin = makeAddr("operationalAdmin");
address account = makeAddr("account");

Migrator internal _migrator;
MockERC20 internal _oldToken;
MockERC20 internal _newToken;
MockGlobals internal _globals;

function setUp() external {
_globals = new MockGlobals();

_oldToken = new MockERC20("Old Token", "OT", 18);
_newToken = new MockERC20("New Token", "NT", 18);

_globals.__setOperationalAdmin(operationalAdmin);
}

function testFuzz_tokenSplitScalar(uint256 amount_, uint16 scalar_) external {
vm.assume(scalar_ > 0);

amount_ = bound(amount_, 1, OLD_SUPPLY);

uint256 newAmount = amount_ * scalar_;

_migrator = new Migrator(address(_oldToken), address(_newToken), scalar_);
_migrator = new Migrator(address(_globals), address(_oldToken), address(_newToken), scalar_);

vm.prank(operationalAdmin);
_migrator.setActive(true);

_newToken.mint(address(_migrator), OLD_SUPPLY * scalar_);

Expand All @@ -304,5 +395,5 @@ contract TokenSplitScalars is Test {
assertEq(_newToken.balanceOf(address(this)), newAmount);
assertEq(_newToken.balanceOf(address(_migrator)), (OLD_SUPPLY * scalar_) - newAmount);
}

}
17 changes: 17 additions & 0 deletions tests/mocks/Mocks.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.7;

contract MockGlobals {

address public governor;
address public operationalAdmin;

function __setGovernor(address governor_) external {
governor = governor_;
}

function __setOperationalAdmin(address operationalAdmin_) external {
operationalAdmin = operationalAdmin_;
}

}
Loading