| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161 |
114×
114×
114×
114×
114×
114×
98×
98×
427×
217×
3×
3×
3×
3×
3×
103×
424×
318×
424×
206×
| // SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.3;
import "./interfaces/IStaker.sol";
import "./interfaces/ILongShort.sol";
import "./interfaces/ISyntheticToken.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
/**
@title SyntheticToken
@notice An ERC20 token that tracks or inversely tracks the price of an
underlying asset with floating exposure.
@dev Logic for price tracking contained in LongShort.sol.
The contract inherits from ERC20PresetMinterPauser.sol
*/
contract SyntheticToken is ISyntheticToken, ERC20, ERC20Burnable, AccessControl, ERC20Permit {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
/// @notice Address of the LongShort contract, a deployed LongShort.sol
address public immutable longShort;
/// @notice Address of the Staker contract, a deployed Staker.sol
address public immutable staker;
/// @notice Identifies which market in longShort the token is for.
uint32 public immutable marketIndex;
/// @notice Whether the token is a long token or short token for its market.
bool public immutable isLong;
/// @notice Creates an instance of the contract.
/// @dev Should only be called by TokenFactory.sol for our system.
/// @param name The name of the token.
/// @param symbol The symbol for the token.
/// @param _longShort Address of the core LongShort contract.
/// @param _staker Address of the staker contract.
/// @param _marketIndex Which market the token is for.
/// @param _isLong Whether the token is long or short for its market.
constructor(
string memory name,
string memory symbol,
address _longShort,
address _staker,
uint32 _marketIndex,
bool _isLong
) ERC20(name, symbol) ERC20Permit(name) {
longShort = _longShort;
staker = _staker;
marketIndex = _marketIndex;
isLong = _isLong;
_setupRole(DEFAULT_ADMIN_ROLE, _longShort);
_setupRole(MINTER_ROLE, _longShort);
}
/// @notice Allows users to stake their synthetic tokens to earn Float.
/// @dev Core staking logic contained in Staker.sol
/// @param amount Amount to stake in wei.
function stake(uint256 amount) external override {
// NOTE: this is safe, this function will throw "ERC20: transfer
// amount exceeds balance" if amount exceeds users balance.
super._transfer(msg.sender, address(staker), amount);
IStaker(staker).stakeFromUser(msg.sender, amount);
}
/*╔══════════════════════════════════════════════════════╗
║ FUNCTIONS INHERITED BY ERC20PresetMinterPauser ║
╚══════════════════════════════════════════════════════╝*/
function totalSupply() public view virtual override(ERC20, ISyntheticToken) returns (uint256) {
return ERC20.totalSupply();
}
/**
@notice Mints a number of synthetic tokens for an address.
@dev Can only be called by addresses with a minter role.
This should correspond to the Long Short contract.
@param to The address for which to mint the tokens for.
@param amount Amount of synthetic tokens to mint in wei.
*/
function mint(address to, uint256 amount) external override onlyRole(MINTER_ROLE) {
_mint(to, amount);
}
/// @notice Burns or destroys a number of held synthetic tokens for an address.
/// @dev Modified to only allow Long Short to burn tokens on redeem.
/// @param amount The amount of tokens to burn in wei.
function burn(uint256 amount) public override(ERC20Burnable, ISyntheticToken) {
Erequire(msg.sender == longShort, "Only LongShort contract");
super._burn(_msgSender(), amount);
}
/**
@notice Overrides the default ERC20 transferFrom.
@dev To allow users to avoid approving LongShort when redeeming tokens,
longShort has a virtual infinite allowance.
@param sender User for which to transfer tokens.
@param recipient Recipient of the transferred tokens.
@param amount Amount of tokens to transfer in wei.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) public override(ERC20, ISyntheticToken) returns (bool) {
Eif (recipient == longShort && msg.sender == longShort) {
// If it to longShort and msg.sender is longShort don't perform additional transfer checks.
ERC20._transfer(sender, recipient, amount);
return true;
} else {
return ERC20.transferFrom(sender, recipient, amount);
}
}
function transfer(address recipient, uint256 amount)
public
virtual
override(ERC20, ISyntheticToken)
returns (bool)
{
return ERC20.transfer(recipient, amount);
}
/**
@notice Overrides the OpenZeppelin _beforeTokenTransfer hook
@dev Ensures that this contract's accounting reflects all the senders's outstanding
tokens from next price actions before any token transfer occurs.
Removal of pausing functionality of ERC20PresetMinterPausable is intentional.
@param sender User for which tokens are to be transferred for.
*/
function _beforeTokenTransfer(
address sender,
address to,
uint256 amount
) internal override {
if (sender != longShort) {
ILongShort(longShort).executeOutstandingNextPriceSettlementsUser(sender, marketIndex);
}
super._beforeTokenTransfer(sender, to, amount);
}
/**
@notice Gets the synthetic token balance of the user in wei.
@dev To automatically account for next price actions which have been confirmed but not settled,
includes any outstanding tokens owed by longShort.
@param account The address for which to get the balance of.
*/
function balanceOf(address account) public view virtual override returns (uint256) {
return
ERC20.balanceOf(account) +
ILongShort(longShort).getUsersConfirmedButNotSettledSynthBalance(
account,
marketIndex,
isLong
);
}
}
|