diff --git a/contracts/StableAssetFactory.sol b/contracts/StableAssetFactory.sol new file mode 100644 index 0000000..bc86fa8 --- /dev/null +++ b/contracts/StableAssetFactory.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/math/SafeMathUpgradeable.sol"; +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; + +import "./StableAsset.sol"; +import "./TapETH.sol"; +import "./misc/ConstantExchangeRateProvider.sol"; + +/** + * @title StableAsset Application + * @author Nuts Finance Developer + * @notice The StableSwap Application provides an interface for users to interact with StableSwap pool contracts + * @dev The StableSwap Application contract allows users to mint pool tokens, swap between different tokens, and redeem pool tokens to underlying tokens. + * This contract should never store assets. + */ +contract StableAssetFactory is Initializable, ReentrancyGuardUpgradeable { + using SafeMathUpgradeable for uint256; + using SafeERC20Upgradeable for IERC20Upgradeable; + + struct CreatePoolArgument { + address tokenA; + address tokenB; + uint256 precisionA; + uint256 precisionB; + uint256 mintFee; + uint256 swapFee; + uint256 redeemFee; + uint256 A; + } + + /** + * @dev This event is emitted when the governance is modified. + * @param governance is the new value of the governance. + */ + event GovernanceModified(address governance); + + /** + * @dev This event is emitted when the governance is modified. + * @param governance is the new value of the governance. + */ + event GovernanceProposed(address governance); + + /** + * @dev This event is emitted when a new pool is created. + * @param poolToken is the pool token created. + */ + event PoolCreated(address proxyAdmin, address poolToken, address stableAsset); + + /** + * @dev This is the account that has governance control over the StableAssetApplication contract. + */ + address public governance; + + /** + * @dev Pending governance address, + */ + address public pendingGovernance; + + address public stableAssetImplentation; + address public tapETHImplentation; + ConstantExchangeRateProvider public constantExchangeRateProvider; + + /** + * @dev Initializes the StableSwap Application contract. + */ + function initialize( + address _stableAssetImplentation, + address _tapETHImplentation + ) public initializer { + __ReentrancyGuard_init(); + governance = msg.sender; + stableAssetImplentation = _stableAssetImplentation; + tapETHImplentation = _tapETHImplentation; + constantExchangeRateProvider = new ConstantExchangeRateProvider(); + } + + /** + * @dev Propose the govenance address. + * @param _governance Address of the new governance. + */ + function proposeGovernance(address _governance) public { + require(msg.sender == governance, "not governance"); + pendingGovernance = _governance; + emit GovernanceProposed(_governance); + } + + /** + * @dev Accept the govenance address. + */ + function acceptGovernance() public { + require(msg.sender == pendingGovernance, "not pending governance"); + governance = pendingGovernance; + pendingGovernance = address(0); + emit GovernanceModified(governance); + } + + function createPool(CreatePoolArgument calldata argument) public { + ProxyAdmin proxyAdmin = new ProxyAdmin(); + proxyAdmin.transferOwnership(msg.sender); + bytes memory tapETHInit = abi.encodeCall( + TapETH.initialize, + (address(this)) + ); + TransparentUpgradeableProxy tapETHProxy = new TransparentUpgradeableProxy( + address(tapETHImplentation), + address(proxyAdmin), + tapETHInit + ); + + address[] memory tokens = new address[](2); + uint256[] memory precisions = new uint256[](2); + uint256[] memory fees = new uint256[](3); + tokens[0] = argument.tokenA; + tokens[1] = argument.tokenB; + precisions[0] = argument.precisionA; + precisions[1] = argument.precisionB; + fees[0] = argument.mintFee; + fees[1] = argument.swapFee; + fees[2] = argument.redeemFee; + uint256 A = argument.A; + uint256 exchangeRateTokenIndex = 1; + + bytes memory stableAssetInit = abi.encodeCall( + StableAsset.initialize, + ( + tokens, + precisions, + fees, + TapETH(address(tapETHProxy)), + A, + constantExchangeRateProvider, + exchangeRateTokenIndex + ) + ); + TransparentUpgradeableProxy stableAssetProxy = new TransparentUpgradeableProxy( + address(stableAssetImplentation), + address(proxyAdmin), + stableAssetInit + ); + StableAsset stableAsset = StableAsset(address(stableAssetProxy)); + TapETH tapETH = TapETH(address(tapETHProxy)); + + stableAsset.proposeGovernance(msg.sender); + tapETH.addPool(address(stableAsset)); + tapETH.proposeGovernance(msg.sender); + emit PoolCreated( + address(proxyAdmin), + address(tapETHProxy), + address(stableAssetProxy) + ); + } +} diff --git a/docs/contracts/StableAsset.md b/docs/contracts/StableAsset.md index 030a9ae..7d416a2 100644 --- a/docs/contracts/StableAsset.md +++ b/docs/contracts/StableAsset.md @@ -104,23 +104,6 @@ function exchangeRateTokenIndex() external view returns (uint256) *Index of tokens array for IExchangeRateProvider.* -#### Returns - -| Name | Type | Description | -|---|---|---| -| _0 | uint256 | undefined | - -### feeErrorMargin - -```solidity -function feeErrorMargin() external view returns (uint256) -``` - - - -*Fee error margin.* - - #### Returns | Name | Type | Description | @@ -386,23 +369,6 @@ function initialize(address[] _tokens, uint256[] _precisions, uint256[] _fees, c | _exchangeRateProvider | contract IExchangeRateProvider | undefined | | _exchangeRateTokenIndex | uint256 | undefined | -### maxDeltaD - -```solidity -function maxDeltaD() external view returns (uint256) -``` - - - -*Max delta D.* - - -#### Returns - -| Name | Type | Description | -|---|---|---| -| _0 | uint256 | undefined | - ### mint ```solidity @@ -821,71 +787,6 @@ function updateA(uint256 _futureA, uint256 _futureABlock) external nonpayable | _futureA | uint256 | The new A value. | | _futureABlock | uint256 | The block number to update A value. | -### updateFeeErrorMargin - -```solidity -function updateFeeErrorMargin(uint256 newValue) external nonpayable -``` - - - -*update fee error margin.* - -#### Parameters - -| Name | Type | Description | -|---|---|---| -| newValue | uint256 | undefined | - -### updateMaxDeltaDMargin - -```solidity -function updateMaxDeltaDMargin(uint256 newValue) external nonpayable -``` - - - -*update yield error margin.* - -#### Parameters - -| Name | Type | Description | -|---|---|---| -| newValue | uint256 | undefined | - -### updateYieldErrorMargin - -```solidity -function updateYieldErrorMargin(uint256 newValue) external nonpayable -``` - - - -*update yield error margin.* - -#### Parameters - -| Name | Type | Description | -|---|---|---| -| newValue | uint256 | undefined | - -### yieldErrorMargin - -```solidity -function yieldErrorMargin() external view returns (uint256) -``` - - - -*Yield error margin.* - - -#### Returns - -| Name | Type | Description | -|---|---|---| -| _0 | uint256 | undefined | - ## Events @@ -924,22 +825,6 @@ event FeeCollected(uint256 feeAmount, uint256 totalSupply) | feeAmount | uint256 | is the amount of fee collected. | | totalSupply | uint256 | is the total supply of LP token. | -### FeeMarginModified - -```solidity -event FeeMarginModified(uint256 margin) -``` - - - -*This event is emitted when the fee margin is modified.* - -#### Parameters - -| Name | Type | Description | -|---|---|---| -| margin | uint256 | is the new value of the margin. | - ### GovernanceModified ```solidity @@ -988,22 +873,6 @@ event Initialized(uint8 version) |---|---|---| | version | uint8 | undefined | -### MaxDeltaDModified - -```solidity -event MaxDeltaDModified(uint256 delta) -``` - - - -*This event is emitted when the max delta D is modified.* - -#### Parameters - -| Name | Type | Description | -|---|---|---| -| delta | uint256 | is the new value of the delta. | - ### MintFeeModified ```solidity @@ -1128,43 +997,10 @@ event YieldCollected(uint256[] amounts, uint256 feeAmount, uint256 totalSupply) | feeAmount | uint256 | is the amount of yield collected. | | totalSupply | uint256 | is the total supply of LP token. | -### YieldMarginModified - -```solidity -event YieldMarginModified(uint256 margin) -``` - - - -*This event is emitted when the fee margin is modified.* - -#### Parameters - -| Name | Type | Description | -|---|---|---| -| margin | uint256 | is the new value of the margin. | - ## Errors -### ImbalancedPool - -```solidity -error ImbalancedPool(uint256 oldD, uint256 newD) -``` - - - - - -#### Parameters - -| Name | Type | Description | -|---|---|---| -| oldD | uint256 | undefined | -| newD | uint256 | undefined | - ### InsufficientMintAmount ```solidity diff --git a/docs/contracts/elin/contracts/interfaces/IERC1822Proxiable.md b/docs/contracts/elin/contracts/interfaces/IERC1822Proxiable.md new file mode 100644 index 0000000..c7220d8 --- /dev/null +++ b/docs/contracts/elin/contracts/interfaces/IERC1822Proxiable.md @@ -0,0 +1,32 @@ +# IERC1822Proxiable + + + + + + + +*ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified proxy whose upgrades are fully controlled by the current implementation.* + +## Methods + +### proxiableUUID + +```solidity +function proxiableUUID() external view returns (bytes32) +``` + + + +*Returns the storage slot that the proxiable contract assumes is being used to store the implementation address. IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this function revert if invoked through a proxy.* + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + + + + diff --git a/docs/contracts/elin/contracts/interfaces/IERC1967.md b/docs/contracts/elin/contracts/interfaces/IERC1967.md new file mode 100644 index 0000000..01698f3 --- /dev/null +++ b/docs/contracts/elin/contracts/interfaces/IERC1967.md @@ -0,0 +1,64 @@ +# IERC1967 + + + + + + + +*ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC. _Available since v4.8.3._* + + +## Events + +### AdminChanged + +```solidity +event AdminChanged(address previousAdmin, address newAdmin) +``` + + + +*Emitted when the admin account has changed.* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| previousAdmin | address | undefined | +| newAdmin | address | undefined | + +### BeaconUpgraded + +```solidity +event BeaconUpgraded(address indexed beacon) +``` + + + +*Emitted when the beacon is changed.* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| beacon `indexed` | address | undefined | + +### Upgraded + +```solidity +event Upgraded(address indexed implementation) +``` + + + +*Emitted when the implementation is upgraded.* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| implementation `indexed` | address | undefined | + + + diff --git a/test/StableAssetFactory.ts b/test/StableAssetFactory.ts new file mode 100644 index 0000000..b13704e --- /dev/null +++ b/test/StableAssetFactory.ts @@ -0,0 +1,64 @@ +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import { expect } from "chai"; +import { ethers, upgrades, web3 } from "hardhat"; + +describe("StableAssetFactory", function () { + it("create pool", async () => { + const StableAssetFactory = await ethers.getContractFactory("StableAssetFactory"); + const StableAsset = await ethers.getContractFactory("StableAsset"); + const TapETH = await ethers.getContractFactory("TapETH"); + const stableAssetImpl = await StableAsset.deploy(); + const tapETHImpl = await TapETH.deploy(); + + /// Deploy swap and tokens + const factory = await upgrades.deployProxy(StableAssetFactory, [stableAssetImpl.address, tapETHImpl.address]); + const MockToken = await ethers.getContractFactory("MockToken"); + /// Deploy token1 with name "test 1", symbol "T1", decimals 18 + const token1 = await MockToken.deploy("test 1", "T1", 18); + /// Deploy token2 with name "test 2", symbol "T2", decimals 18 + const token2 = await MockToken.deploy("test 2", "T2", 18); + const [owner, feeRecipient, user] = await ethers.getSigners(); + + const args = { + tokenA: token1.address, + tokenB: token2.address, + precisionA: 1, + precisionB: 1, + mintFee: 0, + swapFee: 0, + redeemFee: 0, + A: 100, + } + + const tx = await factory.createPool(args); + const receipt = await tx.wait(); + const event = receipt.events?.filter((x) => { + return x.event == "PoolCreated" + }); + const poolToken = event[0].args.poolToken; + const stableAsset = event[0].args.stableAsset; + + const poolTokenDeployed = TapETH.attach(poolToken); + const stableAssetDeployed = StableAsset.attach(stableAsset); + await poolTokenDeployed.acceptGovernance(); + await stableAssetDeployed.acceptGovernance(); + + await stableAssetDeployed.unpause(); + /// Mint 100 token1 to user + await token1.mint(user.address, web3.utils.toWei("100")); + /// Mint 100 token2 to user + await token2.mint(user.address, web3.utils.toWei("100")); + /// Approve swap contract to spend 100 token1 + await token1.connect(user).approve(stableAssetDeployed.address, web3.utils.toWei("100")); + /// Approve swap contract to spend 100 token2 + await token2.connect(user).approve(stableAssetDeployed.address, web3.utils.toWei("100")); + + await stableAssetDeployed.connect(user).mint([web3.utils.toWei("100"), + web3.utils.toWei("100"),], 0); + + console.log(await poolTokenDeployed.decimals()); + console.log(await poolTokenDeployed.name()); + console.log(await poolTokenDeployed.totalSupply()); + console.log(await stableAssetDeployed.totalSupply()); + }); +});