-
Notifications
You must be signed in to change notification settings - Fork 15
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
Rework to the wrapped token approach #9
Changes from all 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 |
---|---|---|
|
@@ -2,4 +2,5 @@ build/ | |
.env | ||
.idea/ | ||
node_modules/ | ||
|
||
coverage/ | ||
coverage.json |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
// SPDX-License-Identifier: CC0-1.0 | ||
|
||
pragma solidity 0.8.7; | ||
|
||
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol"; | ||
import "./utils/Claimable.sol"; | ||
import "./utils/PausableEIP1967Admin.sol"; | ||
import "./interfaces/IERC677.sol"; | ||
import "./interfaces/IERC677Receiver.sol"; | ||
|
||
/** | ||
* @title SBCToken | ||
* @dev Wrapped token used for depositing into SBC. | ||
*/ | ||
contract SBCToken is IERC677, ERC20Pausable, PausableEIP1967Admin, Claimable { | ||
address private _minter; | ||
|
||
constructor() ERC20("", "") {} | ||
|
||
/** | ||
* @dev Initialization setter for the minter address. | ||
* Only admin can call this method. | ||
* @param minter address of the SBCWrapper contract. | ||
*/ | ||
function setMinter(address minter) external onlyAdmin { | ||
require(_minter == address(0), "SBCToken: minter already set"); | ||
_minter = minter; | ||
} | ||
|
||
/** | ||
* @dev Mints new tokens. | ||
* Only configured minter is allowed to mint tokens, which should be a SBCWrapper contract. | ||
* @param _to tokens receiver. | ||
* @param _amount amount of tokens to mint. | ||
*/ | ||
function mint(address _to, uint256 _amount) external { | ||
require(_msgSender() == _minter, "SBCToken: not a minter"); | ||
_mint(_to, _amount); | ||
} | ||
|
||
/** | ||
* @dev Implements the ERC677 transferAndCall standard. | ||
* Executes a regular transfer, but calls the receiver's function to handle them in the same transaction. | ||
* @param _to tokens receiver. | ||
* @param _amount amount of sent tokens. | ||
* @param _data extra data to pass to the callback function. | ||
*/ | ||
function transferAndCall( | ||
address _to, | ||
uint256 _amount, | ||
bytes calldata _data | ||
) external override { | ||
address sender = _msgSender(); | ||
_transfer(sender, _to, _amount); | ||
require(IERC677Receiver(_to).onTokenTransfer(sender, _amount, _data), "SBCToken: ERC677 callback failed"); | ||
} | ||
|
||
/** | ||
* @dev Allows to transfer any locked token from this contract. | ||
* Only admin can call this method. | ||
* @param _token address of the token, if it is not provided (0x00..00), native coins will be transferred. | ||
* @param _to address that will receive the locked tokens from this contract. | ||
*/ | ||
function claimTokens(address _token, address _to) external onlyAdmin { | ||
_claimValues(_token, _to); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// SPDX-License-Identifier: CC0-1.0 | ||
|
||
pragma solidity 0.8.7; | ||
|
||
import "./utils/EIP1967Proxy.sol"; | ||
import "./SBCToken.sol"; | ||
|
||
/** | ||
* @title SBCTokenProxy | ||
* @dev Upgradeable version of the underlying SBCToken. | ||
*/ | ||
contract SBCTokenProxy is EIP1967Proxy { | ||
mapping(address => uint256) private _balances; | ||
|
||
mapping(address => mapping(address => uint256)) private _allowances; | ||
|
||
uint256 private _totalSupply; | ||
|
||
string private _name; | ||
string private _symbol; | ||
|
||
constructor( | ||
address _admin, | ||
string memory name, | ||
string memory symbol | ||
) { | ||
_setAdmin(_admin); | ||
_setImplementation(address(new SBCToken())); | ||
_name = name; | ||
_symbol = symbol; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
// SPDX-License-Identifier: CC0-1.0 | ||
|
||
pragma solidity 0.8.7; | ||
|
||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; | ||
import "./utils/PausableEIP1967Admin.sol"; | ||
import "./SBCToken.sol"; | ||
|
||
/** | ||
* @title SBCWrapper | ||
* @dev Wrapper engine contract for minting wrapped tokens that can be deposited into SBC. | ||
* Used for wrapping of STAKE and other possible ERC20 tokens. | ||
*/ | ||
contract SBCWrapper is IERC677Receiver, PausableEIP1967Admin, Claimable, ReentrancyGuard { | ||
using SafeERC20 for IERC20; | ||
|
||
enum TokenStatus { | ||
DISABLED, | ||
ENABLED, | ||
PAUSED | ||
} | ||
|
||
mapping(address => TokenStatus) public tokenStatus; | ||
// if tokenRate[A] = X, then user will receive Y * X / 10**18 wrapped tokens for locking Y of A tokens. | ||
mapping(address => uint256) public tokenRate; | ||
|
||
SBCToken public immutable sbcToken; | ||
|
||
event Swap(address indexed token, address indexed user, uint256 amount, uint256 received); | ||
event UpdateSwapRate(address indexed token, uint256 rate); | ||
|
||
constructor(SBCToken _sbcToken) { | ||
sbcToken = _sbcToken; | ||
} | ||
|
||
/** | ||
* @dev Enables swapping of new token into wrapped SBC token at a given rate. | ||
* Only admin can call this method. | ||
* @param _token address of the enabled or reenabled token contract. | ||
* @param _rate exchange rate for the new pair, multiplied by 10**18. | ||
*/ | ||
function enableToken(address _token, uint256 _rate) external onlyAdmin { | ||
require(_rate > 0, "SBCWrapper: invalid rate"); | ||
|
||
tokenStatus[_token] = TokenStatus.ENABLED; | ||
tokenRate[_token] = _rate; | ||
|
||
emit UpdateSwapRate(_token, _rate); | ||
} | ||
|
||
/** | ||
* @dev Temporary pauses swapping of some particular token, which can be reenaled later. | ||
* Only admin can call this method. | ||
* @param _token address of the paused token contract. | ||
*/ | ||
function pauseToken(address _token) external onlyAdmin { | ||
tokenStatus[_token] = TokenStatus.PAUSED; | ||
} | ||
|
||
/** | ||
* @dev Swaps some of the whitelisted tokens for the newly created wrapped tokens. | ||
* Tokens must be pre-approved before calling this function. | ||
* @param _token address of the swapped token contract. | ||
* @param _amount amount of tokens to swap. | ||
* @param _permitData optional permit calldata to use for preliminary token approval. | ||
* supports STAKE permit and EIP2612 standards. | ||
*/ | ||
function swap( | ||
address _token, | ||
uint256 _amount, | ||
bytes calldata _permitData | ||
) external nonReentrant whenNotPaused { | ||
require(tokenStatus[_token] == TokenStatus.ENABLED, "SBCWrapper: token is not enabled"); | ||
|
||
if (_permitData.length > 4) { | ||
// supported signatures: | ||
// permit(address,address,uint256,uint256,bool,uint8,bytes32,bytes32) | ||
// permit(address,address,uint256,uint256,uint8,bytes32,bytes32) | ||
require( | ||
bytes4(_permitData[0:4]) == bytes4(0x8fcbaf0c) || bytes4(_permitData[0:4]) == bytes4(0xd505accf), | ||
"SBCWrapper: invalid permit signature" | ||
); | ||
(bool status, ) = _token.call(_permitData); | ||
require(status, "SBCWrapper: permit failed"); | ||
} | ||
|
||
address sender = _msgSender(); | ||
|
||
// We do not plan to support any deflationary or rebasing tokens in this contract | ||
// so it is not required to check that ERC20 balance has indeed change. | ||
// It is an admin responsibility to carefully check that enabled token correctly implements ERC20 standard. | ||
IERC20(_token).safeTransferFrom(sender, address(this), _amount); | ||
|
||
_swapTokens(sender, _token, _amount); | ||
} | ||
|
||
/** | ||
* @dev ERC677 callback for swapping tokens in the simpler way during transferAndCall. | ||
* @param from address of the received token contract. | ||
* @param value amount of the received tokens. | ||
*/ | ||
function onTokenTransfer( | ||
address from, | ||
uint256 value, | ||
bytes calldata | ||
) external override nonReentrant whenNotPaused returns (bool) { | ||
address token = _msgSender(); | ||
require(tokenStatus[token] == TokenStatus.ENABLED, "SBCWrapper: token is not enabled"); | ||
|
||
_swapTokens(from, token, value); | ||
return true; | ||
} | ||
|
||
/** | ||
* @dev Allows to transfer any locked token from this contract. | ||
* Only admin can call this method. | ||
* While it is not allowed to claim previously enabled or paused tokens, | ||
* the admin should still verify that the claimed token is a valid ERC20 token contract. | ||
* @param _token address of the token, if it is not provided (0x00..00), native coins will be transferred. | ||
* @param _to address that will receive the locked tokens on this contract. | ||
*/ | ||
function claimTokens(address _token, address _to) external onlyAdmin { | ||
require(tokenStatus[_token] == TokenStatus.DISABLED, "SBCWrapper: token already swappable"); | ||
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. Consider to add a comment that the admin needs to make sure that the claimable token does not have the same storage with a token configured for the swap (the situation when different tokens addresses refers to the same storage contract). 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. Sure. |
||
|
||
_claimValues(_token, _to); | ||
} | ||
|
||
function _swapTokens( | ||
address _receiver, | ||
address _token, | ||
uint256 _amount | ||
) internal { | ||
uint256 acquired = (_amount * tokenRate[_token]) / 1 ether; | ||
require(acquired > 0, "SBCWrapper: invalid amount"); | ||
|
||
sbcToken.mint(_receiver, acquired); | ||
|
||
emit Swap(_token, _receiver, _amount, acquired); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// SPDX-License-Identifier: CC0-1.0 | ||
|
||
pragma solidity 0.8.7; | ||
|
||
import "./utils/EIP1967Proxy.sol"; | ||
import "./SBCWrapper.sol"; | ||
|
||
/** | ||
* @title SBCWrapperProxy | ||
* @dev Upgradeable version of the underlying SBCWrapper. | ||
*/ | ||
contract SBCWrapperProxy is EIP1967Proxy { | ||
constructor(address _admin, SBCToken _token) { | ||
_setAdmin(_admin); | ||
_setImplementation(address(new SBCWrapper(_token))); | ||
} | ||
} |
This file was deleted.
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.
Consider either to make the code safer by adding a check that the swap contract balance increased by expected amount of tokens or to extend the code with a comment that this check is not here because the contract admin must be sure that such tokens are not added for swap.
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.
Sure, added a comment