Skip to content

Commit

Permalink
feat(adapter): Add RGT to Tribe migration adapter [SIM-52] (#188)
Browse files Browse the repository at this point in the history
* add RGT to Tribe migration adapter
  • Loading branch information
FlattestWhite authored Apr 5, 2022
1 parent 698e991 commit ca5c2ab
Show file tree
Hide file tree
Showing 7 changed files with 455 additions and 1 deletion.
54 changes: 54 additions & 0 deletions contracts/mocks/external/TribePegExchangerMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/

pragma solidity 0.6.10;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";

/**
* @title Contract to exchange RGT with TRIBE post-merger
*/
contract TribePegExchangerMock {
using SafeERC20 for IERC20;

/// @notice the multiplier applied to RGT before converting to TRIBE scaled up by 1e9
uint256 public constant exchangeRate = 26705673430; // 26.7 TRIBE / RGT
/// @notice the granularity of the exchange rate
uint256 public constant scalar = 1e9;

event Exchange(address indexed from, uint256 amountIn, uint256 amountOut);

address public immutable rgt;
address public immutable tribe;

constructor(address _rgt, address _tribe) public {
rgt = _rgt;
tribe = _tribe;
}

/// @notice call to exchange held RGT with TRIBE
/// @param amount the amount to exchange
/// Mirrors the real contract without the permission state checks.
function exchange(uint256 amount) public {
uint256 tribeOut = amount * exchangeRate / scalar;
IERC20(rgt).safeTransferFrom(msg.sender, address(this), amount);
IERC20(tribe).safeTransfer(msg.sender, tribeOut);
emit Exchange(msg.sender, amount, tribeOut);
}
}
99 changes: 99 additions & 0 deletions contracts/protocol/integration/wrap/RgtMigrationWrapAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/

pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";

/**
* @title RgtMigrationWrapAdater
* @author FlattestWhite
*
* Wrap adapter for one time token migration that returns data for wrapping RGT into TRIBE.
* Note: RGT can not be unwrapped into TRIBE, because migration can not be reversed.
*/
contract RgtMigrationWrapAdapter {

/* ============ State Variables ============ */

address public immutable pegExchanger;

/* ============ Constructor ============ */

/**
* Set state variables
*
* @param _pegExchanger Address of PegExchanger contract
*/
constructor(
address _pegExchanger
)
public
{
pegExchanger = _pegExchanger;
}

/* ============ External Getter Functions ============ */

/**
* Generates the calldata to migrate RGT tokens to TRIBE tokens.
*
* @param _underlyingUnits Total quantity of underlying units to wrap
*
* @return address Target contract address
* @return uint256 Total quantity of underlying units (if underlying is ETH)
* @return bytes Wrap calldata
*/
function getWrapCallData(
address /* _underlyingToken */,
address /* _wrappedToken */,
uint256 _underlyingUnits
)
external
view
returns (address, uint256, bytes memory)
{
// exchange(uint256 amount)
bytes memory callData = abi.encodeWithSignature("exchange(uint256)", _underlyingUnits);

return (pegExchanger, 0, callData);
}

/**
* This function will revert, since migration cannot be reversed.
*/
function getUnwrapCallData(
address /* _underlyingToken */,
address /* _wrappedToken */,
uint256 /* _wrappedTokenUnits */
)
external
pure
returns (address, uint256, bytes memory)
{
revert("RGT migration cannot be reversed");
}

/**
* Returns the address to approve source tokens for wrapping.
*
* @return address Address of the contract to approve tokens to
*/
function getSpenderAddress(address /* _underlyingToken */, address /* _wrappedToken */) external view returns(address) {
return pegExchanger;
}
}
174 changes: 174 additions & 0 deletions test/integration/rgtMigrationWrapModule.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import "module-alias/register";
import { BigNumber } from "ethers";

import { Address } from "@utils/types";
import { Account } from "@utils/test/types";
import { ADDRESS_ZERO, ZERO } from "@utils/constants";
import { RgtMigrationWrapAdapter, SetToken, StandardTokenMock, TribePegExchangerMock, WrapModule } from "@utils/contracts";
import DeployHelper from "@utils/deploys";
import {
ether,
} from "@utils/index";
import {
addSnapshotBeforeRestoreAfterEach,
getAccounts,
getWaffleExpect,
getSystemFixture,
} from "@utils/test/index";
import { SystemFixture } from "@utils/fixtures";

const expect = getWaffleExpect();

describe("rgtMigrationWrapModule", () => {
let owner: Account;
let deployer: DeployHelper;
let setup: SystemFixture;

let wrapModule: WrapModule;

let rgtToken: StandardTokenMock;
let tribeToken: StandardTokenMock;
let pegExchanger: TribePegExchangerMock;
let adapter: RgtMigrationWrapAdapter;

const rgtMigrationWrapAdapterIntegrationName: string = "RGT_MIGRATION_WRAPPER";
const exchangeRate = 26705673430;
const scalar = 1e9;

before(async () => {
[
owner,
] = await getAccounts();

// System setup
deployer = new DeployHelper(owner.wallet);
setup = getSystemFixture(owner.address);
await setup.initialize();

// WrapModule setup
wrapModule = await deployer.modules.deployWrapModule(setup.controller.address, setup.weth.address);
await setup.controller.addModule(wrapModule.address);

rgtToken = await deployer.mocks.deployTokenMock(owner.address);
tribeToken = await deployer.mocks.deployTokenMock(owner.address);

// RgtMigrationWrapV2Adapter setup
pegExchanger = await deployer.mocks.deployTribePegExchangerMock(rgtToken.address, tribeToken.address);
adapter = await deployer.adapters.deployRgtMigrationWrapAdapter(pegExchanger.address);

await setup.integrationRegistry.addIntegration(wrapModule.address, rgtMigrationWrapAdapterIntegrationName, adapter.address);
});

addSnapshotBeforeRestoreAfterEach();

context("when a SetToken has been deployed and issued", async () => {
let setToken: SetToken;
let setTokensIssued: BigNumber;

before(async () => {
setToken = await setup.createSetToken(
[rgtToken.address],
[BigNumber.from(10 ** 8)],
[setup.issuanceModule.address, wrapModule.address]
);

// Initialize modules
await setup.issuanceModule.initialize(setToken.address, ADDRESS_ZERO);
await wrapModule.initialize(setToken.address);

// Issue some Sets
setTokensIssued = ether(10);
await rgtToken.approve(setup.issuanceModule.address, BigNumber.from(10 ** 9));
await setup.issuanceModule.issue(setToken.address, setTokensIssued, owner.address);
});

describe("#wrap", async () => {
let subjectSetToken: Address;
let subjectUnderlyingToken: Address;
let subjectWrappedToken: Address;
let subjectUnderlyingUnits: BigNumber;
let subjectIntegrationName: string;
let subjectCaller: Account;

beforeEach(async () => {
subjectSetToken = setToken.address;
subjectUnderlyingToken = rgtToken.address;
subjectWrappedToken = tribeToken.address;
subjectUnderlyingUnits = BigNumber.from(10 ** 8);
subjectIntegrationName = rgtMigrationWrapAdapterIntegrationName;
subjectCaller = owner;

await tribeToken.mint(pegExchanger.address, BigNumber.from(10 ** 9).mul(exchangeRate).div(scalar));
});

async function subject(): Promise<any> {
return wrapModule.connect(subjectCaller.wallet).wrap(
subjectSetToken,
subjectUnderlyingToken,
subjectWrappedToken,
subjectUnderlyingUnits,
subjectIntegrationName
);
}

it("should convert underlying balance of RGT tokens to TRIBE tokens * multiplier", async () => {
const previousRgtTokenBalance = await rgtToken.balanceOf(setToken.address);
const previousTribeTokenBalance = await tribeToken.balanceOf(setToken.address);
expect(previousRgtTokenBalance).to.eq(BigNumber.from(10 ** 9));
expect(previousTribeTokenBalance).to.eq(ZERO);

await subject();

const rgtTokenBalance = await rgtToken.balanceOf(setToken.address);
const tribeTokenBalance = await tribeToken.balanceOf(setToken.address);
const components = await setToken.getComponents();

expect(rgtTokenBalance).to.eq(ZERO);
expect(tribeTokenBalance).to.eq(previousRgtTokenBalance.mul(exchangeRate).div(scalar));
expect(components.length).to.eq(1);
});
});

describe("#unwrap", async () => {
let subjectSetToken: Address;
let subjectUnderlyingToken: Address;
let subjectWrappedToken: Address;
let subjectWrappedUnits: BigNumber;
let subjectIntegrationName: string;
let subjectCaller: Account;

beforeEach(async () => {
subjectSetToken = setToken.address;
subjectUnderlyingToken = rgtToken.address;
subjectWrappedToken = tribeToken.address;
subjectWrappedUnits = BigNumber.from(10 ** 8);
subjectIntegrationName = rgtMigrationWrapAdapterIntegrationName;
subjectCaller = owner;

await tribeToken.mint(pegExchanger.address, BigNumber.from(10 ** 9).mul(exchangeRate).div(scalar));

await wrapModule.connect(subjectCaller.wallet).wrap(
subjectSetToken,
subjectUnderlyingToken,
subjectWrappedToken,
subjectWrappedUnits,
subjectIntegrationName
);
});

async function subject(): Promise<any> {
return wrapModule.connect(subjectCaller.wallet).unwrap(
subjectSetToken,
subjectUnderlyingToken,
subjectWrappedToken,
subjectWrappedUnits,
subjectIntegrationName
);
}

it("should revert", async () => {
await expect(subject()).to.be.revertedWith("RGT migration cannot be reversed");
});
});
});
});
Loading

0 comments on commit ca5c2ab

Please sign in to comment.