Skip to content

Commit ca5c2ab

Browse files
feat(adapter): Add RGT to Tribe migration adapter [SIM-52] (#188)
* add RGT to Tribe migration adapter
1 parent 698e991 commit ca5c2ab

File tree

7 files changed

+455
-1
lines changed

7 files changed

+455
-1
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
Copyright 2021 Set Labs Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache License, Version 2.0
17+
*/
18+
19+
pragma solidity 0.6.10;
20+
21+
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
22+
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
23+
24+
/**
25+
* @title Contract to exchange RGT with TRIBE post-merger
26+
*/
27+
contract TribePegExchangerMock {
28+
using SafeERC20 for IERC20;
29+
30+
/// @notice the multiplier applied to RGT before converting to TRIBE scaled up by 1e9
31+
uint256 public constant exchangeRate = 26705673430; // 26.7 TRIBE / RGT
32+
/// @notice the granularity of the exchange rate
33+
uint256 public constant scalar = 1e9;
34+
35+
event Exchange(address indexed from, uint256 amountIn, uint256 amountOut);
36+
37+
address public immutable rgt;
38+
address public immutable tribe;
39+
40+
constructor(address _rgt, address _tribe) public {
41+
rgt = _rgt;
42+
tribe = _tribe;
43+
}
44+
45+
/// @notice call to exchange held RGT with TRIBE
46+
/// @param amount the amount to exchange
47+
/// Mirrors the real contract without the permission state checks.
48+
function exchange(uint256 amount) public {
49+
uint256 tribeOut = amount * exchangeRate / scalar;
50+
IERC20(rgt).safeTransferFrom(msg.sender, address(this), amount);
51+
IERC20(tribe).safeTransfer(msg.sender, tribeOut);
52+
emit Exchange(msg.sender, amount, tribeOut);
53+
}
54+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
Copyright 2021 Set Labs Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache License, Version 2.0
17+
*/
18+
19+
pragma solidity 0.6.10;
20+
pragma experimental "ABIEncoderV2";
21+
22+
/**
23+
* @title RgtMigrationWrapAdater
24+
* @author FlattestWhite
25+
*
26+
* Wrap adapter for one time token migration that returns data for wrapping RGT into TRIBE.
27+
* Note: RGT can not be unwrapped into TRIBE, because migration can not be reversed.
28+
*/
29+
contract RgtMigrationWrapAdapter {
30+
31+
/* ============ State Variables ============ */
32+
33+
address public immutable pegExchanger;
34+
35+
/* ============ Constructor ============ */
36+
37+
/**
38+
* Set state variables
39+
*
40+
* @param _pegExchanger Address of PegExchanger contract
41+
*/
42+
constructor(
43+
address _pegExchanger
44+
)
45+
public
46+
{
47+
pegExchanger = _pegExchanger;
48+
}
49+
50+
/* ============ External Getter Functions ============ */
51+
52+
/**
53+
* Generates the calldata to migrate RGT tokens to TRIBE tokens.
54+
*
55+
* @param _underlyingUnits Total quantity of underlying units to wrap
56+
*
57+
* @return address Target contract address
58+
* @return uint256 Total quantity of underlying units (if underlying is ETH)
59+
* @return bytes Wrap calldata
60+
*/
61+
function getWrapCallData(
62+
address /* _underlyingToken */,
63+
address /* _wrappedToken */,
64+
uint256 _underlyingUnits
65+
)
66+
external
67+
view
68+
returns (address, uint256, bytes memory)
69+
{
70+
// exchange(uint256 amount)
71+
bytes memory callData = abi.encodeWithSignature("exchange(uint256)", _underlyingUnits);
72+
73+
return (pegExchanger, 0, callData);
74+
}
75+
76+
/**
77+
* This function will revert, since migration cannot be reversed.
78+
*/
79+
function getUnwrapCallData(
80+
address /* _underlyingToken */,
81+
address /* _wrappedToken */,
82+
uint256 /* _wrappedTokenUnits */
83+
)
84+
external
85+
pure
86+
returns (address, uint256, bytes memory)
87+
{
88+
revert("RGT migration cannot be reversed");
89+
}
90+
91+
/**
92+
* Returns the address to approve source tokens for wrapping.
93+
*
94+
* @return address Address of the contract to approve tokens to
95+
*/
96+
function getSpenderAddress(address /* _underlyingToken */, address /* _wrappedToken */) external view returns(address) {
97+
return pegExchanger;
98+
}
99+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import "module-alias/register";
2+
import { BigNumber } from "ethers";
3+
4+
import { Address } from "@utils/types";
5+
import { Account } from "@utils/test/types";
6+
import { ADDRESS_ZERO, ZERO } from "@utils/constants";
7+
import { RgtMigrationWrapAdapter, SetToken, StandardTokenMock, TribePegExchangerMock, WrapModule } from "@utils/contracts";
8+
import DeployHelper from "@utils/deploys";
9+
import {
10+
ether,
11+
} from "@utils/index";
12+
import {
13+
addSnapshotBeforeRestoreAfterEach,
14+
getAccounts,
15+
getWaffleExpect,
16+
getSystemFixture,
17+
} from "@utils/test/index";
18+
import { SystemFixture } from "@utils/fixtures";
19+
20+
const expect = getWaffleExpect();
21+
22+
describe("rgtMigrationWrapModule", () => {
23+
let owner: Account;
24+
let deployer: DeployHelper;
25+
let setup: SystemFixture;
26+
27+
let wrapModule: WrapModule;
28+
29+
let rgtToken: StandardTokenMock;
30+
let tribeToken: StandardTokenMock;
31+
let pegExchanger: TribePegExchangerMock;
32+
let adapter: RgtMigrationWrapAdapter;
33+
34+
const rgtMigrationWrapAdapterIntegrationName: string = "RGT_MIGRATION_WRAPPER";
35+
const exchangeRate = 26705673430;
36+
const scalar = 1e9;
37+
38+
before(async () => {
39+
[
40+
owner,
41+
] = await getAccounts();
42+
43+
// System setup
44+
deployer = new DeployHelper(owner.wallet);
45+
setup = getSystemFixture(owner.address);
46+
await setup.initialize();
47+
48+
// WrapModule setup
49+
wrapModule = await deployer.modules.deployWrapModule(setup.controller.address, setup.weth.address);
50+
await setup.controller.addModule(wrapModule.address);
51+
52+
rgtToken = await deployer.mocks.deployTokenMock(owner.address);
53+
tribeToken = await deployer.mocks.deployTokenMock(owner.address);
54+
55+
// RgtMigrationWrapV2Adapter setup
56+
pegExchanger = await deployer.mocks.deployTribePegExchangerMock(rgtToken.address, tribeToken.address);
57+
adapter = await deployer.adapters.deployRgtMigrationWrapAdapter(pegExchanger.address);
58+
59+
await setup.integrationRegistry.addIntegration(wrapModule.address, rgtMigrationWrapAdapterIntegrationName, adapter.address);
60+
});
61+
62+
addSnapshotBeforeRestoreAfterEach();
63+
64+
context("when a SetToken has been deployed and issued", async () => {
65+
let setToken: SetToken;
66+
let setTokensIssued: BigNumber;
67+
68+
before(async () => {
69+
setToken = await setup.createSetToken(
70+
[rgtToken.address],
71+
[BigNumber.from(10 ** 8)],
72+
[setup.issuanceModule.address, wrapModule.address]
73+
);
74+
75+
// Initialize modules
76+
await setup.issuanceModule.initialize(setToken.address, ADDRESS_ZERO);
77+
await wrapModule.initialize(setToken.address);
78+
79+
// Issue some Sets
80+
setTokensIssued = ether(10);
81+
await rgtToken.approve(setup.issuanceModule.address, BigNumber.from(10 ** 9));
82+
await setup.issuanceModule.issue(setToken.address, setTokensIssued, owner.address);
83+
});
84+
85+
describe("#wrap", async () => {
86+
let subjectSetToken: Address;
87+
let subjectUnderlyingToken: Address;
88+
let subjectWrappedToken: Address;
89+
let subjectUnderlyingUnits: BigNumber;
90+
let subjectIntegrationName: string;
91+
let subjectCaller: Account;
92+
93+
beforeEach(async () => {
94+
subjectSetToken = setToken.address;
95+
subjectUnderlyingToken = rgtToken.address;
96+
subjectWrappedToken = tribeToken.address;
97+
subjectUnderlyingUnits = BigNumber.from(10 ** 8);
98+
subjectIntegrationName = rgtMigrationWrapAdapterIntegrationName;
99+
subjectCaller = owner;
100+
101+
await tribeToken.mint(pegExchanger.address, BigNumber.from(10 ** 9).mul(exchangeRate).div(scalar));
102+
});
103+
104+
async function subject(): Promise<any> {
105+
return wrapModule.connect(subjectCaller.wallet).wrap(
106+
subjectSetToken,
107+
subjectUnderlyingToken,
108+
subjectWrappedToken,
109+
subjectUnderlyingUnits,
110+
subjectIntegrationName
111+
);
112+
}
113+
114+
it("should convert underlying balance of RGT tokens to TRIBE tokens * multiplier", async () => {
115+
const previousRgtTokenBalance = await rgtToken.balanceOf(setToken.address);
116+
const previousTribeTokenBalance = await tribeToken.balanceOf(setToken.address);
117+
expect(previousRgtTokenBalance).to.eq(BigNumber.from(10 ** 9));
118+
expect(previousTribeTokenBalance).to.eq(ZERO);
119+
120+
await subject();
121+
122+
const rgtTokenBalance = await rgtToken.balanceOf(setToken.address);
123+
const tribeTokenBalance = await tribeToken.balanceOf(setToken.address);
124+
const components = await setToken.getComponents();
125+
126+
expect(rgtTokenBalance).to.eq(ZERO);
127+
expect(tribeTokenBalance).to.eq(previousRgtTokenBalance.mul(exchangeRate).div(scalar));
128+
expect(components.length).to.eq(1);
129+
});
130+
});
131+
132+
describe("#unwrap", async () => {
133+
let subjectSetToken: Address;
134+
let subjectUnderlyingToken: Address;
135+
let subjectWrappedToken: Address;
136+
let subjectWrappedUnits: BigNumber;
137+
let subjectIntegrationName: string;
138+
let subjectCaller: Account;
139+
140+
beforeEach(async () => {
141+
subjectSetToken = setToken.address;
142+
subjectUnderlyingToken = rgtToken.address;
143+
subjectWrappedToken = tribeToken.address;
144+
subjectWrappedUnits = BigNumber.from(10 ** 8);
145+
subjectIntegrationName = rgtMigrationWrapAdapterIntegrationName;
146+
subjectCaller = owner;
147+
148+
await tribeToken.mint(pegExchanger.address, BigNumber.from(10 ** 9).mul(exchangeRate).div(scalar));
149+
150+
await wrapModule.connect(subjectCaller.wallet).wrap(
151+
subjectSetToken,
152+
subjectUnderlyingToken,
153+
subjectWrappedToken,
154+
subjectWrappedUnits,
155+
subjectIntegrationName
156+
);
157+
});
158+
159+
async function subject(): Promise<any> {
160+
return wrapModule.connect(subjectCaller.wallet).unwrap(
161+
subjectSetToken,
162+
subjectUnderlyingToken,
163+
subjectWrappedToken,
164+
subjectWrappedUnits,
165+
subjectIntegrationName
166+
);
167+
}
168+
169+
it("should revert", async () => {
170+
await expect(subject()).to.be.revertedWith("RGT migration cannot be reversed");
171+
});
172+
});
173+
});
174+
});

0 commit comments

Comments
 (0)