-
Notifications
You must be signed in to change notification settings - Fork 95
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(adapter): Add RGT to Tribe migration adapter [SIM-52] (#188)
* add RGT to Tribe migration adapter
- Loading branch information
1 parent
698e991
commit ca5c2ab
Showing
7 changed files
with
455 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
99
contracts/protocol/integration/wrap/RgtMigrationWrapAdapter.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
}); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.