-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathShares.sol
227 lines (203 loc) · 9.47 KB
/
Shares.sol
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
/**
* SPDX-License-Identifier: LicenseRef-Aktionariat
*
* MIT License with Automated License Fee Payments
*
* Copyright (c) 2022 Aktionariat AG (aktionariat.com)
*
* Permission is hereby granted to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software
* without restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* - The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* - All automated license fee payments integrated into this and related Software
* are preserved.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity ^0.8.0;
import "../ERC20/ERC20Named.sol";
import "../ERC20/ERC20PermitLight.sol";
import "../ERC20/ERC20Permit2.sol";
import "../ERC20/IERC677Receiver.sol";
import "../recovery/ERC20Recoverable.sol";
import "../shares/IShares.sol";
/**
* @title CompanyName AG Shares
* @author Luzius Meisser, [email protected]
*
* These tokens represent ledger-based securities according to article 973d of the Swiss Code of Obligations.
* This smart contract serves as an ownership registry, enabling the token holders to register them as
* shareholders in the issuer's shareholder registry. This is equivalent to the traditional system
* of having physical share certificates kept at home by the shareholders and a shareholder registry run by
* the company. Just like with physical certificates, the owners of the tokens are the owners of the shares.
* However, in order to exercise their rights (for example receive a dividend), shareholders must register
* themselves. For example, in case the company pays out a dividend to a previous shareholder because
* the current shareholder did not register, the company cannot be held liable for paying the dividend to
* the "wrong" shareholder. In relation to the company, only the registered shareholders count as such.
*/
contract Shares is ERC20Recoverable, ERC20Named, ERC20PermitLight, ERC20Permit2, IShares{
// Version history:
// 1: everything before 2022-07-19
// 2: added mintMany and mintManyAndCall, added VERSION field
// 3: added permit
// 4: refactor to custom errors, added allowance for permit2
uint8 public constant VERSION = 4;
string public terms;
uint256 public override totalShares; // total number of shares, maybe not all tokenized
uint256 public invalidTokens;
event Announcement(string message);
event TokensDeclaredInvalid(address indexed holder, uint256 amount, string message);
event ChangeTerms(string terms);
event ChangeTotalShares(uint256 total);
constructor(
string memory _symbol,
string memory _name,
string memory _terms,
uint256 _totalShares,
address _owner,
IRecoveryHub _recoveryHub,
Permit2Hub _permit2Hub
)
ERC20Named(_symbol, _name, 0, _owner)
ERC20Recoverable(_recoveryHub)
ERC20PermitLight()
ERC20Permit2(_permit2Hub)
{
totalShares = _totalShares;
terms = _terms;
invalidTokens = 0;
_recoveryHub.setRecoverable(false);
}
function setTerms(string memory _terms) external onlyOwner {
terms = _terms;
emit ChangeTerms(_terms);
}
/**
* Declares the number of total shares, including those that have not been tokenized and those
* that are held by the company itself. This number can be substiantially higher than totalSupply()
* in case not all shares have been tokenized. Also, it can be lower than totalSupply() in case some
* tokens have become invalid.
*/
function setTotalShares(uint256 _newTotalShares) external onlyOwner() {
uint256 _totalValidSupply = totalValidSupply();
if (_newTotalShares < _totalValidSupply) {
revert Shares_InvalidTotalShares(_totalValidSupply, _newTotalShares);
}
totalShares = _newTotalShares;
emit ChangeTotalShares(_newTotalShares);
}
/**
* Allows the issuer to make public announcements that are visible on the blockchain.
*/
function announcement(string calldata message) external onlyOwner() {
emit Announcement(message);
}
/**
* See parent method for collateral requirements.
*/
function setCustomClaimCollateral(IERC20 collateral, uint256 rate) external onlyOwner() {
super._setCustomClaimCollateral(collateral, rate);
}
function getClaimDeleter() public override view returns (address) {
return owner;
}
/**
* Signals that the indicated tokens have been declared invalid (e.g. by a court ruling in accordance
* with article 973g of the Swiss Code of Obligations) and got detached from
* the underlying shares. Invalid tokens do not carry any shareholder rights any more.
*
* This function is purely declarative. It does not technically immobilize the affected tokens as
* that would give the issuer too much power.
*/
function declareInvalid(address holder, uint256 amount, string calldata message) external onlyOwner() {
uint256 holderBalance = balanceOf(holder);
if (amount > holderBalance) {
revert ERC20InsufficientBalance(holder, holderBalance, amount);
}
invalidTokens += amount;
emit TokensDeclaredInvalid(holder, amount, message);
}
/**
* The total number of valid tokens in circulation. In case some tokens have been declared invalid, this
* number might be lower than totalSupply(). Also, it will always be lower than or equal to totalShares().
*/
function totalValidSupply() public view returns (uint256) {
return totalSupply() - invalidTokens;
}
/**
* Allows the company to tokenize shares and transfer them e.g to the draggable contract and wrap them.
* If these shares are newly created, setTotalShares must be called first in order to adjust the total number of shares.
*/
function mintAndCall(address shareholder, address callee, uint256 amount, bytes calldata data) external {
mint(callee, amount);
if (!IERC677Receiver(callee).onTokenTransfer(shareholder, amount, data)) {
revert IERC677Receiver.IERC677_OnTokenTransferFailed();
}
}
function mintManyAndCall(address[] calldata target, address callee, uint256[] calldata amount, bytes calldata data) external {
uint256 len = target.length;
if (len != amount.length) {
revert Shares_UnequalLength(len, amount.length);
}
uint256 total = 0;
for (uint256 i = 0; i<len; i++){
total += amount[i];
}
mint(callee, total);
for (uint256 i = 0; i<len; i++){
if(!IERC677Receiver(callee).onTokenTransfer(target[i], amount[i], data)){
revert IERC677Receiver.IERC677_OnTokenTransferFailed();
}
}
}
function mint(address target, uint256 amount) public onlyOwner {
_mint(target, amount);
}
function mintMany(address[] calldata target, uint256[] calldata amount) public onlyOwner {
uint256 len = target.length;
if (len != amount.length) {
revert Shares_UnequalLength(len, amount.length);
}
for (uint256 i = 0; i<len; i++){
_mint(target[i], amount[i]);
}
}
function _mint(address account, uint256 amount) internal virtual override {
uint256 newValidSupply = totalValidSupply() + amount;
if (newValidSupply > totalShares) {
revert Shares_InsufficientTotalShares(totalShares, newValidSupply);
}
super._mint(account, amount);
}
function transfer(address to, uint256 value) virtual override(ERC20Recoverable, ERC20Flaggable, IERC20) public returns (bool) {
return super.transfer(to, value);
}
/**
* Transfers _amount tokens to the company and burns them.
* The meaning of this operation depends on the circumstances and the fate of the shares does
* not necessarily follow the fate of the tokens. For example, the company itself might call
* this function to implement a formal decision to destroy some of the outstanding shares.
* Also, this function might be called by an owner to return the shares to the company and
* get them back in another form under an according agreement (e.g. printed certificates or
* tokens on a different blockchain). It is not recommended to call this function without
* having agreed with the company on the further fate of the shares in question.
*/
function burn(uint256 _amount) override external {
_transfer(msg.sender, address(this), _amount);
_burn(address(this), _amount);
}
function allowance(address owner, address spender) public view virtual override(ERC20Permit2, ERC20Flaggable, IERC20) returns (uint256) {
return super.allowance(owner, spender);
}
}