-
Notifications
You must be signed in to change notification settings - Fork 53
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
Pool to support rebasing mint call #1490
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity 0.8.24; | ||
|
||
import {IBurnMintERC20} from "../../shared/token/ERC20/IBurnMintERC20.sol"; | ||
|
||
import {Pool} from "../libraries/Pool.sol"; | ||
import {BurnWithFromMintTokenPool} from "./BurnWithFromMintTokenPool.sol"; | ||
|
||
/// @notice This pool mints and burns a 3rd-party token. | ||
/// @dev This contract is a variant of BurnMintTokenPool that uses `burn(from, amount)`. | ||
/// @dev This contract supports minting tokens that do not mint the exact amount they are asked to mint. This can be | ||
/// used for rebasing tokens. NOTE: for true rebasing support, the lockOrBurn method must also be updated to support | ||
/// relaying the correct amount. | ||
contract BurnWithFromMintRebasingTokenPool is BurnWithFromMintTokenPool { | ||
error NegativeMintAmount(uint256 amountBurned); | ||
|
||
string public constant override typeAndVersion = "BurnWithFromMintRebasingTokenPool 1.5.0"; | ||
|
||
constructor( | ||
IBurnMintERC20 token, | ||
address[] memory allowlist, | ||
address rmnProxy, | ||
address router | ||
) BurnWithFromMintTokenPool(token, allowlist, rmnProxy, router) {} | ||
|
||
/// @notice Mint tokens from the pool to the recipient | ||
/// @dev The _validateReleaseOrMint check is an essential security check | ||
function releaseOrMint( | ||
Pool.ReleaseOrMintInV1 calldata releaseOrMintIn | ||
) external virtual override returns (Pool.ReleaseOrMintOutV1 memory) { | ||
_validateReleaseOrMint(releaseOrMintIn); | ||
|
||
uint256 balancePre = IBurnMintERC20(address(i_token)).balanceOf(releaseOrMintIn.receiver); | ||
|
||
// Mint to the receiver | ||
IBurnMintERC20(address(i_token)).mint(releaseOrMintIn.receiver, releaseOrMintIn.amount); | ||
|
||
uint256 balancePost = IBurnMintERC20(address(i_token)).balanceOf(releaseOrMintIn.receiver); | ||
|
||
// Mint should not reduce the number of tokens in the receiver, if it does it will revert the call. | ||
if (balancePost < balancePre) { | ||
revert NegativeMintAmount(balancePre - balancePost); | ||
} | ||
|
||
emit Minted(msg.sender, releaseOrMintIn.receiver, balancePost - balancePre); | ||
|
||
return Pool.ReleaseOrMintOutV1({destinationAmount: balancePost - balancePre}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity 0.8.24; | ||
|
||
import {ERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/ERC20.sol"; | ||
|
||
contract ERC20RebasingHelper is ERC20 { | ||
uint16 public s_multiplierPercentage = 100; | ||
bool public s_mintShouldBurn = false; | ||
|
||
constructor() ERC20("Rebasing", "REB") {} | ||
|
||
function mint(address to, uint256 amount) external { | ||
if (!s_mintShouldBurn) { | ||
_mint(to, amount * s_multiplierPercentage / 100); | ||
return; | ||
} | ||
_burn(to, amount * s_multiplierPercentage / 100); | ||
} | ||
|
||
function setMultiplierPercentage(uint16 multiplierPercentage) external { | ||
s_multiplierPercentage = multiplierPercentage; | ||
} | ||
|
||
function setMintShouldBurn(bool mintShouldBurn) external { | ||
s_mintShouldBurn = mintShouldBurn; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity 0.8.24; | ||
|
||
import {IBurnMintERC20} from "../../../shared/token/ERC20/IBurnMintERC20.sol"; | ||
|
||
import {Pool} from "../../libraries/Pool.sol"; | ||
import {BurnWithFromMintRebasingTokenPool} from "../../pools/BurnWithFromMintRebasingTokenPool.sol"; | ||
import {BurnMintSetup} from "./BurnMintSetup.t.sol"; | ||
|
||
import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; | ||
import {ERC20RebasingHelper} from "../helpers/ERC20RebasingHelper.sol"; | ||
|
||
contract BurnWithFromMintRebasingTokenPoolSetup is BurnMintSetup { | ||
BurnWithFromMintRebasingTokenPool internal s_pool; | ||
ERC20RebasingHelper internal s_rebasingToken; | ||
|
||
function setUp() public virtual override { | ||
BurnMintSetup.setUp(); | ||
|
||
s_rebasingToken = new ERC20RebasingHelper(); | ||
|
||
s_pool = new BurnWithFromMintRebasingTokenPool( | ||
IBurnMintERC20(address(s_rebasingToken)), new address[](0), address(s_mockRMN), address(s_sourceRouter) | ||
); | ||
|
||
_applyChainUpdates(address(s_pool)); | ||
|
||
deal(address(s_rebasingToken), OWNER, 1e18); | ||
|
||
vm.startPrank(s_burnMintOffRamp); | ||
} | ||
} | ||
|
||
contract BurnWithFromMintTokenPool_releaseOrMint is BurnWithFromMintRebasingTokenPoolSetup { | ||
function test_Setup_Success() public view { | ||
assertEq(address(s_rebasingToken), address(s_pool.getToken())); | ||
assertEq(address(s_mockRMN), s_pool.getRmnProxy()); | ||
assertEq(false, s_pool.getAllowListEnabled()); | ||
assertEq(type(uint256).max, s_rebasingToken.allowance(address(s_pool), address(s_pool))); | ||
assertEq("BurnWithFromMintRebasingTokenPool 1.5.0", s_pool.typeAndVersion()); | ||
} | ||
|
||
function test_releaseOrMint_Success() public { | ||
uint256 amount = 1000; | ||
uint256 balancePre = s_rebasingToken.balanceOf(address(OWNER)); | ||
|
||
Pool.ReleaseOrMintOutV1 memory releaseOrMintOut = s_pool.releaseOrMint(_getReleaseOrMintIn(amount)); | ||
|
||
assertEq(amount, releaseOrMintOut.destinationAmount); | ||
assertEq(balancePre + amount, s_rebasingToken.balanceOf(address(OWNER))); | ||
} | ||
|
||
function test_releaseOrMint_rebasing_success(uint16 multiplierPercentage) public { | ||
RensR marked this conversation as resolved.
Show resolved
Hide resolved
|
||
uint256 amount = 1000; | ||
uint256 expectedAmount = amount * multiplierPercentage / 100; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. q: if percentage is 0, this should still succeed right because There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct, I would call that case valid. The check is mostly there so 1) don't allow theft and 2) to not revert with an underflow when calculating the returned value |
||
s_rebasingToken.setMultiplierPercentage(multiplierPercentage); | ||
|
||
uint256 balancePre = s_rebasingToken.balanceOf(address(OWNER)); | ||
|
||
Pool.ReleaseOrMintOutV1 memory releaseOrMintOut = s_pool.releaseOrMint(_getReleaseOrMintIn(amount)); | ||
|
||
assertEq(expectedAmount, releaseOrMintOut.destinationAmount); | ||
assertEq(balancePre + expectedAmount, s_rebasingToken.balanceOf(address(OWNER))); | ||
} | ||
|
||
function test_releaseOrMint_NegativeMintAmount_reverts() public { | ||
uint256 amount = 1000; | ||
s_rebasingToken.setMintShouldBurn(true); | ||
|
||
vm.expectRevert(abi.encodeWithSelector(BurnWithFromMintRebasingTokenPool.NegativeMintAmount.selector, amount)); | ||
|
||
s_pool.releaseOrMint(_getReleaseOrMintIn(amount)); | ||
} | ||
|
||
function _getReleaseOrMintIn(uint256 amount) internal view returns (Pool.ReleaseOrMintInV1 memory) { | ||
return Pool.ReleaseOrMintInV1({ | ||
originalSender: bytes(""), | ||
receiver: OWNER, | ||
amount: amount, | ||
localToken: address(s_rebasingToken), | ||
remoteChainSelector: DEST_CHAIN_SELECTOR, | ||
sourcePoolAddress: abi.encode(s_remoteBurnMintPool), | ||
sourcePoolData: "", | ||
offchainTokenData: "" | ||
}); | ||
} | ||
} |
Large diffs are not rendered by default.
Large diffs are not rendered by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The other way of writing it cannot be overridden