Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions contracts/draggable/ERC20Cancelled.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ contract ERC20Cancelled is ERC20Flaggable {
* the ones it has, leaving the minority with draggable shares that wrap
* cancelled tokens.
*/
function migrateWithQuorum() public {
SHA.migrate();
function migrateWithQuorum(uint256 totalVotes) public {
SHA.migrate(totalVotes);
uint256 predecessorBalance = SHA.balanceOf(address(this));
SHA.unwrap(predecessorBalance);
_burn(address(this), predecessorBalance);
Expand All @@ -87,9 +87,9 @@ contract ERC20Cancelled is ERC20Flaggable {
* 3. Call burnBaseToken from any address.
*
*/
function burnThemAll() external {
function burnThemAll(uint256 totalVotes) external {
mintToSHA();
migrateWithQuorum();
migrateWithQuorum(totalVotes);
burnBaseToken();
}
}
19 changes: 7 additions & 12 deletions contracts/draggable/ERC20Draggable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -246,24 +246,23 @@ abstract contract ERC20Draggable is IERC677Receiver, IDraggable, ERC20Flaggable
emit ChangeOracle(oracle);
}

function migrateWithExternalApproval(address successor, uint256 additionalVotes) external override onlyOracle {
function migrateWithExternalApproval(address successor, uint256 additionalVotes, uint256 totalVotes) external override onlyOracle {
// Additional votes cannot be higher than the votes not represented by these tokens.
// The assumption here is that more shareholders are bound to the shareholder agreement
// that this contract helps enforce and a vote among all parties is necessary to change
// it, with an oracle counting and reporting the votes of the others.
if (totalSupply() + additionalVotes > totalVotingTokens()) {
revert Draggable_TooManyVotes(totalVotingTokens(), totalSupply() + additionalVotes);
if (totalSupply() + additionalVotes > totalVotes) {
revert Draggable_TooManyVotes(totalVotes, totalSupply() + additionalVotes);
}
_migrate(successor, additionalVotes);
_migrate(successor, additionalVotes, totalVotes);
}

function migrate() external override {
_migrate(msg.sender, 0);
function migrate(uint256 totalVotes) external override {
_migrate(msg.sender, 0, totalVotes);
}

function _migrate(address successor, uint256 additionalVotes) internal {
function _migrate(address successor, uint256 additionalVotes, uint256 totalVotes) internal {
uint256 yesVotes = additionalVotes + balanceOf(successor);
uint256 totalVotes = totalVotingTokens();
if (yesVotes > totalVotes) {
revert Draggable_TooManyVotes(totalVotes, yesVotes);
}
Expand All @@ -282,10 +281,6 @@ abstract contract ERC20Draggable is IERC677Receiver, IDraggable, ERC20Flaggable
return balanceOf(voter);
}

function totalVotingTokens() public view override returns (uint256) {
return IShares(address(wrapped)).totalShares();
}

function _hasVoted(address voter) internal view returns (bool) {
return hasFlagInternal(voter, FLAG_VOTE_HINT);
}
Expand Down
8 changes: 2 additions & 6 deletions contracts/draggable/IDraggable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,8 @@ interface IDraggable is IERC20 {
function drag(address buyer, IERC20 currency) external;
function notifyOfferEnded() external;
function votingPower(address voter) external returns (uint256);
function totalVotingTokens() external view returns (uint256);
function notifyVoted(address voter) external;
function migrate() external;
function setOracle(address newOracle) external;
function migrateWithExternalApproval(address successor, uint256 additionalVotes) external;
function setTerms(string calldata _terms) external;


function migrate(uint256 totalVotes) external;
function migrateWithExternalApproval(address successor, uint256 additionalVotes, uint256 totalVotes) external;
}
23 changes: 2 additions & 21 deletions contracts/draggable/Offer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -172,24 +172,11 @@ contract Offer is IOffer {
}

function isAccepted() public view returns (bool) {
if (isVotingOpen()) {
// is it already clear that more than the quorum requiered will vote yes even though the vote is not over yet?
return yesVotes * BPS_MUL >= quorum * token.totalVotingTokens();
} else {
// did more than the quorum requiered votes say 'yes'?
return yesVotes * BPS_MUL >= quorum * (yesVotes + noVotes);
}
return !isVotingOpen() && (yesVotes * BPS_MUL >= quorum * (yesVotes + noVotes));
}

function isDeclined() public view returns (bool) {
if (isVotingOpen()) {
// is it already clear that 25% will vote no even though the vote is not over yet?
uint256 supply = token.totalVotingTokens();
return (supply - noVotes) * BPS_MUL < quorum * supply;
} else {
// did quorum% of all cast votes say 'no'?
return BPS_MUL * yesVotes < quorum * (yesVotes + noVotes);
}
return !isVotingOpen() && (BPS_MUL * yesVotes < quorum * (yesVotes + noVotes));
}

function notifyMoved(address from, address to, uint256 value) external override onlyToken {
Expand Down Expand Up @@ -228,12 +215,6 @@ contract Offer is IOffer {
* oracle should always report the total number of yes and no votes. Abstentions are not counted.
*/
function reportExternalVotes(uint256 yes, uint256 no) external onlyOracle votingOpen {
uint256 maxVotes = token.totalVotingTokens();
uint256 reportingVotes = yes + no + IERC20(address(token)).totalSupply();
if (reportingVotes > maxVotes) {
revert Offer_TooManyVotes(maxVotes, reportingVotes);
}
// adjust total votes taking into account that the oralce might have reported different counts before
yesVotes = yesVotes - yesExternal + yes;
noVotes = noVotes - noExternal + no;
// remember how the oracle voted in case the oracle later reports updated numbers
Expand Down
1 change: 0 additions & 1 deletion contracts/multichain/MultichainSharesChild.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ contract MultichainSharesChild is ERC20Named, ERC20Recoverable, ERC20PermitLight
constructor(
string memory _name,
string memory _symbol,
string memory _terms,
IRecoveryHub _recoveryHub,
address _owner,
Permit2Hub _permit2Hub,
Expand Down
2 changes: 1 addition & 1 deletion contracts/multichain/MultichainSharesMaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ contract MultichainSharesMaster is AllowlistDraggableShares, CCIPAdministrable {
Permit2Hub _permit2Hub,
address _ccipAdmin
)
AllowlistDraggableShares(_terms, _params, _recoveryHub, _offerFactory, _owner, _permit2Hub)
AllowlistDraggableShares(_params, _recoveryHub, _offerFactory, _owner, _permit2Hub)
CCIPAdministrable(_ccipAdmin)
{

Expand Down
3 changes: 1 addition & 2 deletions contracts/shares/AllowlistDraggableShares.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,13 @@ import "./DraggableShares.sol";
contract AllowlistDraggableShares is DraggableShares, ERC20Allowlistable {

constructor(
string memory _terms,
DraggableParams memory _params,
IRecoveryHub _recoveryHub,
IOfferFactory _offerFactory,
address _oracle,
Permit2Hub _permit2Hub
)
DraggableShares(_terms, _params, _recoveryHub, _offerFactory, _oracle, _permit2Hub)
DraggableShares(_params, _recoveryHub, _offerFactory, _oracle, _permit2Hub)
Ownable(_oracle)
{
// initialization is done in ERC20Allowlistbale and DraggableShares
Expand Down
3 changes: 1 addition & 2 deletions contracts/shares/AllowlistShares.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,11 @@ contract AllowlistShares is Shares, ERC20Allowlistable {
string memory _symbol,
string memory _name,
string memory _terms,
uint256 _totalShares,
IRecoveryHub _recoveryHub,
address _owner,
Permit2Hub _permit2Hub
)
Shares(_symbol, _name, _terms, _totalShares, _owner, _recoveryHub, _permit2Hub)
Shares(_symbol, _name, _terms, _owner, _recoveryHub, _permit2Hub)
ERC20Allowlistable()
{
// initialization in shares
Expand Down
18 changes: 1 addition & 17 deletions contracts/shares/DraggableShares.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,10 @@ contract DraggableShares is ERC20Draggable, ERC20Recoverable, ERC20PermitLight,
// 1: pre permit
// 2: includes permit
// 3: added permit2 allowance, VERSION field
// 4: removed terms, poiting to terms of wrapped token
uint8 public constant VERSION = 3;

string public terms;

/// Event when the terms are changed with setTerms().
event ChangeTerms(string terms);

constructor(
string memory _terms,
DraggableParams memory _params,
IRecoveryHub _recoveryHub,
IOfferFactory _offerFactory,
Expand All @@ -64,7 +59,6 @@ contract DraggableShares is ERC20Draggable, ERC20Recoverable, ERC20PermitLight,
ERC20Recoverable(_recoveryHub)
ERC20Permit2(_permit2Hub)
{
terms = _terms; // to update the terms, migrate to a new contract. That way it is ensured that the terms can only be updated when the quorom agrees.
_recoveryHub.setRecoverable(false);
}

Expand Down Expand Up @@ -99,21 +93,11 @@ contract DraggableShares is ERC20Draggable, ERC20Recoverable, ERC20PermitLight,
}
}

/**
* @notice This function allows the oracle to set the terms.
* @param _terms The new terms.
*/
function setTerms(string calldata _terms) external override onlyOracle {
terms = _terms;
emit ChangeTerms(terms);
}

function _beforeTokenTransfer(address from, address to, uint256 amount) virtual override(ERC20Flaggable, ERC20Draggable) internal {
super._beforeTokenTransfer(from, to, amount);
}

function allowance(address owner, address spender) public view virtual override(ERC20Permit2, ERC20Flaggable, IERC20) returns (uint256) {
return super.allowance(owner, spender);
}

}
7 changes: 3 additions & 4 deletions contracts/shares/DraggableSharesWithPredecessor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,26 +38,25 @@ contract DraggableSharesWithPredecessor is DraggableShares {

constructor(
IDraggable _predecessor,
string memory _terms,
DraggableParams memory _params,
IRecoveryHub _recoveryHub,
IOfferFactory _offerFactory,
address _oracle,
Permit2Hub _permit2Hub
)
DraggableShares(_terms, _params, _recoveryHub, _offerFactory, _oracle, _permit2Hub)
DraggableShares(_params, _recoveryHub, _offerFactory, _oracle, _permit2Hub)
{
predecessor = _predecessor;
}

/**
* @notice This contract needs to hold the majority of the predecessor tokens.
*/
function initiateMigration() external {
function initiateMigration(uint256 totalVotes) external {
uint256 predecessorSupply = predecessor.totalSupply();
_mint(address(predecessor), predecessorSupply);
wrapped = predecessor.wrapped();
predecessor.migrate();
predecessor.migrate(totalVotes);
uint256 predecessorBalance = predecessor.balanceOf(address(this));
predecessor.unwrap(predecessorBalance);
_burn(address(this), predecessorBalance);
Expand Down
7 changes: 3 additions & 4 deletions contracts/shares/DraggableSharesWithPredecessorExternal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,13 @@ contract DraggableSharesWithPredecessorExternal is DraggableShares {

constructor(
IDraggable _predecessor,
string memory _terms,
DraggableParams memory _params,
IRecoveryHub _recoveryHub,
IOfferFactory _offerFactory,
address _oracle,
Permit2Hub _permit2Hub
)
DraggableShares(_terms, _params, _recoveryHub, _offerFactory, _oracle, _permit2Hub)
DraggableShares(_params, _recoveryHub, _offerFactory, _oracle, _permit2Hub)
{
predecessor = _predecessor;
}
Expand All @@ -54,11 +53,11 @@ contract DraggableSharesWithPredecessorExternal is DraggableShares {
* @notice This contract needs to hold the majority of the predecessor tokens and the this contract needs to be the oracle of the predecessor.
*
*/
function initiateMigrationWithExternalApproval(uint256 additionalVotes) external onlyOracle {
function initiateMigrationWithExternalApproval(uint256 additionalVotes, uint256 totalVotes) external onlyOracle {
uint256 predecessorSupply = predecessor.totalSupply();
_mint(address(predecessor), predecessorSupply);
wrapped = predecessor.wrapped();
predecessor.migrateWithExternalApproval(address(this), additionalVotes);
predecessor.migrateWithExternalApproval(address(this), additionalVotes, totalVotes);
uint256 predecessorBalance = predecessor.balanceOf(address(this));
if (predecessorBalance > 0) {
predecessor.unwrap(predecessorBalance);
Expand Down
10 changes: 0 additions & 10 deletions contracts/shares/IShares.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,10 @@ interface IShares is IERC20 {
/*//////////////////////////////////////////////////////////////
Custom errors
//////////////////////////////////////////////////////////////*/
/// New total shares can't be below current valid supply
/// @param totalSupply The current valid supply.
/// @param newTotalShares The new max shares.
error Shares_InvalidTotalShares(uint256 totalSupply, uint256 newTotalShares);
/// Array lengths have to be equal.
/// @param targets Array length of targets.
/// @param amount Array length of amounts.
error Shares_UnequalLength(uint256 targets, uint256 amount);
/// It isn't possible to mint more share token than max shares in existens.
/// @param totalShares The max amount of shares.
/// @param needed The max amount of shares needed (current valid supply + new mint amount).
error Shares_InsufficientTotalShares(uint256 totalShares, uint256 needed);

function burn(uint256) external;

function totalShares() external view returns (uint256);
}
32 changes: 3 additions & 29 deletions contracts/shares/Shares.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,23 +55,19 @@ contract Shares is ERC20Recoverable, ERC20Named, ERC20PermitLight, ERC20Permit2,
// 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;

// 5: removed totalShares
uint8 public constant VERSION = 5;
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
Expand All @@ -80,7 +76,6 @@ contract Shares is ERC20Recoverable, ERC20Named, ERC20PermitLight, ERC20Permit2,
ERC20Recoverable(_recoveryHub)
ERC20Permit2(_permit2Hub)
{
totalShares = _totalShares;
terms = _terms;
invalidTokens = 0;
_recoveryHub.setRecoverable(false);
Expand All @@ -91,22 +86,6 @@ contract Shares is ERC20Recoverable, ERC20Named, ERC20PermitLight, ERC20Permit2,
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.
*/
Expand Down Expand Up @@ -144,15 +123,14 @@ contract Shares is ERC20Recoverable, ERC20Named, ERC20PermitLight, ERC20Permit2,

/**
* 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().
* number might be lower than totalSupply().
*/
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);
Expand Down Expand Up @@ -193,10 +171,6 @@ contract Shares is ERC20Recoverable, ERC20Named, ERC20PermitLight, ERC20Permit2,
}

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);
}

Expand Down
3 changes: 1 addition & 2 deletions contracts/utils/factory/AllowlistDraggableFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ contract AllowlistDraggableFactory is Ownable {
);

return new AllowlistDraggableShares{salt: salt}(
tokenConfig.terms,
params,
manager.recoveryHub(),
manager.offerFactory(),
Expand All @@ -65,7 +64,7 @@ contract AllowlistDraggableFactory is Ownable {
tokenConfig.votePeriod
);

bytes32 initCodeHash = keccak256(abi.encodePacked(type(AllowlistDraggableShares).creationCode, abi.encode(tokenConfig.terms, params, manager.recoveryHub(), manager.offerFactory(), tokenOwner, manager.permit2Hub())));
bytes32 initCodeHash = keccak256(abi.encodePacked(type(AllowlistDraggableShares).creationCode, abi.encode(params, manager.recoveryHub(), manager.offerFactory(), tokenOwner, manager.permit2Hub())));
bytes32 hashResult = keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, initCodeHash));
return address(uint160(uint256(hashResult)));
}
Expand Down
Loading