-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[NES-188] make FakeComponentToken.sol (#3)
- Loading branch information
Showing
4 changed files
with
260 additions
and
10 deletions.
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
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,28 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.25; | ||
|
||
import "forge-std/Script.sol"; | ||
|
||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import { Upgrades } from "openzeppelin-foundry-upgrades/Upgrades.sol"; | ||
|
||
import { FakeComponentToken } from "../src/FakeComponentToken.sol"; | ||
|
||
contract DeployNestContracts is Script { | ||
|
||
address private constant ARC_ADMIN_ADDRESS = 0x1c9d94FAD4ccCd522804a955103899e0D6A4405a; | ||
address private constant USDC_ADDRESS = 0x849c25e6cCB03cdc23ba91d92440dA7bC8486be2; | ||
|
||
function run() external { | ||
vm.startBroadcast(ARC_ADMIN_ADDRESS); | ||
|
||
address fakeComponentTokenProxy = Upgrades.deployUUPSProxy( | ||
"FakeComponentToken.sol", | ||
abi.encodeCall(FakeComponentToken.initialize, (msg.sender, "Banana", "BAN", IERC20(USDC_ADDRESS), 18)) | ||
); | ||
console.log("FakeComponentToken deployed to:", fakeComponentTokenProxy); | ||
|
||
vm.stopBroadcast(); | ||
} | ||
|
||
} |
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,211 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.25; | ||
|
||
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; | ||
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; | ||
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; | ||
import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; | ||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
|
||
import { IComponentToken } from "./interfaces/IComponentToken.sol"; | ||
|
||
/** | ||
* @title FakeComponentToken | ||
* @author Eugene Y. Q. Shen | ||
* @notice Fake example of a ComponentToken that could be used in an AggregateToken when testing. | ||
* Users can buy and sell one FakeComponentToken by exchanging it with one CurrencyToken at any time. | ||
* @custom:oz-upgrades-from FakeComponentToken | ||
*/ | ||
contract FakeComponentToken is | ||
Initializable, | ||
AccessControlUpgradeable, | ||
UUPSUpgradeable, | ||
ERC20Upgradeable, | ||
IComponentToken | ||
{ | ||
|
||
// Storage | ||
|
||
/// @custom:storage-location erc7201:plume.storage.FakeComponentToken | ||
struct FakeComponentTokenStorage { | ||
/// @dev CurrencyToken used to mint and burn the FakeComponentToken | ||
IERC20 currencyToken; | ||
/// @dev Number of decimals of the FakeComponentToken | ||
uint8 decimals; | ||
} | ||
|
||
// keccak256(abi.encode(uint256(keccak256("plume.storage.FakeComponentToken")) - 1)) & ~bytes32(uint256(0xff)) | ||
bytes32 private constant FAKE_COMPONENT_TOKEN_STORAGE_LOCATION = | ||
0x2c4e9dd7fc35b7006b8a84e1ac11ecc9e53a0dd5c8824b364abab355c5037600; | ||
|
||
function _getFakeComponentTokenStorage() private pure returns (FakeComponentTokenStorage storage $) { | ||
assembly { | ||
$.slot := FAKE_COMPONENT_TOKEN_STORAGE_LOCATION | ||
} | ||
} | ||
|
||
// Constants | ||
|
||
/// @notice Role for the upgrader of the FakeComponentToken | ||
bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADE_ROLE"); | ||
|
||
// Events | ||
|
||
/** | ||
* @notice Emitted when a user buys FakeComponentToken using CurrencyToken | ||
* @param user Address of the user who bought the FakeComponentToken | ||
* @param currencyToken CurrencyToken used to buy the FakeComponentToken | ||
* @param currencyTokenAmount Amount of CurrencyToken paid | ||
* @param componentTokenAmount Amount of FakeComponentToken received | ||
*/ | ||
event ComponentTokenBought( | ||
address indexed user, IERC20 indexed currencyToken, uint256 currencyTokenAmount, uint256 componentTokenAmount | ||
); | ||
|
||
/** | ||
* @notice Emitted when a user sells FakeComponentToken to receive CurrencyToken | ||
* @param user Address of the user who sold the FakeComponentToken | ||
* @param currencyToken CurrencyToken received in exchange for the FakeComponentToken | ||
* @param currencyTokenAmount Amount of CurrencyToken received | ||
* @param componentTokenAmount Amount of FakeComponentToken sold | ||
*/ | ||
event ComponentTokenSold( | ||
address indexed user, IERC20 indexed currencyToken, uint256 currencyTokenAmount, uint256 componentTokenAmount | ||
); | ||
|
||
// Errors | ||
|
||
/** | ||
* @notice Indicates a failure because the given CurrencyToken does not match actual CurrencyToken | ||
* @param invalidCurrencyToken CurrencyToken that does not match the actual CurrencyToken | ||
* @param currencyToken Actual CurrencyToken used to mint and burn the FakeComponentToken | ||
*/ | ||
error InvalidCurrencyToken(IERC20 invalidCurrencyToken, IERC20 currencyToken); | ||
|
||
/** | ||
* @notice Indicates a failure because the FakeComponentToken does not have enough CurrencyToken | ||
* @param currencyToken CurrencyToken used to mint and burn the FakeComponentToken | ||
* @param amount Amount of CurrencyToken required in the failed transfer | ||
*/ | ||
error CurrencyTokenInsufficientBalance(IERC20 currencyToken, uint256 amount); | ||
|
||
/** | ||
* @notice Indicates a failure because the user does not have enough CurrencyToken | ||
* @param currencyToken CurrencyToken used to mint and burn the FakeComponentToken | ||
* @param user Address of the user who is selling the CurrencyToken | ||
* @param amount Amount of CurrencyToken required in the failed transfer | ||
*/ | ||
error UserCurrencyTokenInsufficientBalance(IERC20 currencyToken, address user, uint256 amount); | ||
|
||
// Initializer | ||
|
||
/** | ||
* @notice Initialize the FakeComponentToken | ||
* @param owner Address of the owner of the FakeComponentToken | ||
* @param name Name of the FakeComponentToken | ||
* @param symbol Symbol of the FakeComponentToken | ||
* @param currencyToken CurrencyToken used to mint and burn the FakeComponentToken | ||
* @param decimals_ Number of decimals of the FakeComponentToken | ||
*/ | ||
function initialize( | ||
address owner, | ||
string memory name, | ||
string memory symbol, | ||
IERC20 currencyToken, | ||
uint8 decimals_ | ||
) public initializer { | ||
__ERC20_init(name, symbol); | ||
__AccessControl_init(); | ||
__UUPSUpgradeable_init(); | ||
|
||
_grantRole(DEFAULT_ADMIN_ROLE, owner); | ||
_grantRole(UPGRADER_ROLE, owner); | ||
|
||
FakeComponentTokenStorage storage $ = _getFakeComponentTokenStorage(); | ||
$.currencyToken = currencyToken; | ||
$.decimals = decimals_; | ||
} | ||
|
||
// Override Functions | ||
|
||
/** | ||
* @notice Revert when `msg.sender` is not authorized to upgrade the contract | ||
* @param newImplementation Address of the new implementation | ||
*/ | ||
function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE) { } | ||
|
||
/// @notice Number of decimals of the FakeComponentToken | ||
function decimals() public view override returns (uint8) { | ||
FakeComponentTokenStorage storage $ = _getFakeComponentTokenStorage(); | ||
return $.decimals; | ||
} | ||
|
||
// User Functions | ||
|
||
/** | ||
* @notice Buy FakeComponentToken using CurrencyToken | ||
* @dev The user must approve the contract to spend the CurrencyToken | ||
* @param currencyToken_ CurrencyToken used to buy the FakeComponentToken | ||
* @param amount Amount of CurrencyToken to pay to receive the same amount of FakeComponentToken | ||
*/ | ||
function buy(IERC20 currencyToken_, uint256 amount) public returns (uint256) { | ||
FakeComponentTokenStorage storage $ = _getFakeComponentTokenStorage(); | ||
IERC20 currencyToken = $.currencyToken; | ||
|
||
if (currencyToken_ != currencyToken) { | ||
revert InvalidCurrencyToken(currencyToken_, currencyToken); | ||
} | ||
if (!currencyToken.transferFrom(msg.sender, address(this), amount)) { | ||
revert UserCurrencyTokenInsufficientBalance(currencyToken, msg.sender, amount); | ||
} | ||
|
||
_mint(msg.sender, amount); | ||
|
||
emit ComponentTokenBought(msg.sender, currencyToken, amount, amount); | ||
|
||
return amount; | ||
} | ||
|
||
/** | ||
* @notice Sell FakeComponentToken to receive CurrencyToken | ||
* @param currencyToken_ CurrencyToken received in exchange for the FakeComponentToken | ||
* @param amount Amount of CurrencyToken to receive in exchange for the FakeComponentToken | ||
*/ | ||
function sell(IERC20 currencyToken_, uint256 amount) public returns (uint256) { | ||
FakeComponentTokenStorage storage $ = _getFakeComponentTokenStorage(); | ||
IERC20 currencyToken = $.currencyToken; | ||
|
||
if (currencyToken_ != currencyToken) { | ||
revert InvalidCurrencyToken(currencyToken_, currencyToken); | ||
} | ||
if (!currencyToken.transfer(msg.sender, amount)) { | ||
revert CurrencyTokenInsufficientBalance(currencyToken, amount); | ||
} | ||
|
||
_burn(msg.sender, amount); | ||
|
||
emit ComponentTokenSold(msg.sender, currencyToken, amount, amount); | ||
|
||
return amount; | ||
} | ||
|
||
// Admin Setter Functions | ||
|
||
/** | ||
* @notice Set the CurrencyToken used to mint and burn the FakeComponentToken | ||
* @param currencyToken New CurrencyToken | ||
*/ | ||
function setCurrencyToken(IERC20 currencyToken) public onlyRole(DEFAULT_ADMIN_ROLE) { | ||
FakeComponentTokenStorage storage $ = _getFakeComponentTokenStorage(); | ||
$.currencyToken = currencyToken; | ||
} | ||
|
||
// Getter View Functions | ||
|
||
/// @notice CurrencyToken used to mint and burn the FakeComponentToken | ||
function getCurrencyToken() public view returns (IERC20) { | ||
FakeComponentTokenStorage storage $ = _getFakeComponentTokenStorage(); | ||
return $.currencyToken; | ||
} | ||
|
||
} |
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,11 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.25; | ||
|
||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
|
||
interface IComponentToken is IERC20 { | ||
|
||
function buy(IERC20 currencyToken, uint256 currencyTokenAmount) external returns (uint256 componentTokenAmount); | ||
function sell(IERC20 currencyToken, uint256 currencyTokenAmount) external returns (uint256 componentTokenAmount); | ||
|
||
} |