文件 1 的 6:DraggableShares.sol
pragma solidity >=0.8;
import "./ERC20Recoverable.sol";
import "./ERC20Draggable.sol";
contract DraggableShares is ERC20Recoverable, ERC20Draggable {
string public terms;
constructor(string memory _terms, address wrappedToken, uint256 quorumBps, uint256 votePeriodSeconds)
ERC20Draggable(wrappedToken, quorumBps, votePeriodSeconds) {
terms = _terms;
}
function transfer(address to, uint256 value) override(ERC20Recoverable, ERC20) public returns (bool) {
return super.transfer(to, value);
}
function getClaimDeleter() public view override returns (address) {
return IRecoverable(address(wrapped)).getClaimDeleter();
}
function getCollateralRate(address collateralType) public view override returns (uint256) {
uint256 rate = super.getCollateralRate(collateralType);
if (rate > 0) {
return rate;
} else if (collateralType == address(wrapped)) {
return unwrapConversionFactor;
} else {
return IRecoverable(address(wrapped)).getCollateralRate(collateralType) * unwrapConversionFactor;
}
}
}
abstract contract IRecoverable {
function getCollateralRate(address) public virtual view returns (uint256);
function getClaimDeleter() public virtual view returns (address);
}
文件 2 的 6:ERC20.sol
pragma solidity >=0.8;
import "./IERC20.sol";
import "./IERC677Receiver.sol";
abstract contract ERC20 is IERC20 {
mapping (address => uint256) private _balances;
mapping (address => mapping (address => uint256)) private _allowances;
uint256 private _totalSupply;
uint8 public override decimals;
constructor(uint8 _decimals) {
decimals = _decimals;
}
function totalSupply() public view override returns (uint256) {
return _totalSupply;
}
function balanceOf(address account) public view override returns (uint256) {
return _balances[account];
}
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
_transfer(msg.sender, recipient, amount);
return true;
}
function allowance(address owner, address spender) public view override returns (uint256) {
return _allowances[owner][spender];
}
function approve(address spender, uint256 value) public override returns (bool) {
_approve(msg.sender, spender, value);
return true;
}
function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) {
_transfer(sender, recipient, amount);
uint256 currentAllowance = _allowances[sender][msg.sender];
if (currentAllowance < (1 << 255)){
_approve(sender, msg.sender, currentAllowance - amount);
}
return true;
}
function _transfer(address sender, address recipient, uint256 amount) internal virtual {
require(recipient != address(0));
_beforeTokenTransfer(sender, recipient, amount);
_balances[sender] -= amount;
_balances[recipient] += amount;
emit Transfer(sender, recipient, amount);
}
function transferAndCall(address recipient, uint amount, bytes calldata data) public returns (bool) {
bool success = transfer(recipient, amount);
if (success){
success = IERC677Receiver(recipient).onTokenTransfer(msg.sender, amount, data);
}
return success;
}
function _mint(address recipient, uint256 amount) internal virtual {
require(recipient != address(0));
_beforeTokenTransfer(address(0), recipient, amount);
_totalSupply += amount;
_balances[recipient] += amount;
emit Transfer(address(0), recipient, amount);
}
function _burn(address account, uint256 amount) internal virtual {
_beforeTokenTransfer(account, address(0), amount);
_totalSupply -= amount;
_balances[account] -= amount;
emit Transfer(account, address(0), amount);
}
function _approve(address owner, address spender, uint256 value) internal {
_allowances[owner][spender] = value;
emit Approval(owner, spender, value);
}
function _beforeTokenTransfer(address from, address to, uint256 amount) virtual internal;
}
文件 3 的 6:ERC20Draggable.sol
pragma solidity >=0.8;
import "./ERC20.sol";
import "./IERC20.sol";
import "./IERC677Receiver.sol";
contract ERC20Draggable is ERC20, IERC677Receiver {
IERC20 public wrapped;
IOfferFactory public constant factory = IOfferFactory(0xf9f92751F272f0872e2EDb6a280b0990F3e2b8A3);
uint256 private constant MIGRATION_QUORUM = 8000;
uint256 public unwrapConversionFactor = 0;
IOffer public offer;
uint256 public quorum;
uint256 public votePeriod;
event MigrationSucceeded(address newContractAddress);
constructor(
address wrappedToken,
uint256 quorum_,
uint256 votePeriod_
) ERC20(0) {
wrapped = IERC20(wrappedToken);
quorum = quorum_;
votePeriod = votePeriod_;
}
function disableRecovery() public {
IRecoveryDisabler(address(wrapped)).setRecoverable(false);
}
function name() public override view returns (string memory){
if (isBinding()){
return string(abi.encodePacked("Draggable ", wrapped.name()));
} else {
return string(abi.encodePacked("Wrapped ", wrapped.name()));
}
}
function symbol() public override view returns (string memory){
if (isBinding()){
return string(abi.encodePacked("D", wrapped.symbol()));
} else {
return string(abi.encodePacked("W", wrapped.symbol()));
}
}
function onTokenTransfer(address from, uint256 amount, bytes calldata) override public returns (bool) {
require(msg.sender == address(wrapped));
_mint(from, amount);
return true;
}
function wrap(address shareholder, uint256 amount) public {
require(wrapped.transferFrom(msg.sender, address(this), amount));
_mint(shareholder, amount);
}
function isBinding() public view returns (bool) {
return unwrapConversionFactor == 0;
}
function deactivate(uint256 factor) internal {
require(factor >= 1, "factor");
unwrapConversionFactor = factor;
}
function unwrap(uint256 amount) public {
require(!isBinding());
unwrap(msg.sender, amount, unwrapConversionFactor);
}
function unwrap(address owner, uint256 amount, uint256 factor) internal {
_burn(owner, amount);
require(wrapped.transfer(owner, amount * factor));
}
function burn(uint256 amount) public {
_burn(msg.sender, amount);
uint256 factor = isBinding() ? 1 : unwrapConversionFactor;
IBurnable(address(wrapped)).burn(amount * factor);
}
function makeAcquisitionOffer(bytes32 salt, uint256 pricePerShare, address currency) public payable {
require(isBinding());
address newOffer = factory.create{value: msg.value}(salt, msg.sender, pricePerShare, currency, quorum, votePeriod);
if (offerExists()) {
require(IOffer(newOffer).isWellFunded());
offer.contest(newOffer);
}
offer = IOffer(newOffer);
}
function drag(address buyer, address currency) public {
require(msg.sender == address(offer));
unwrap(buyer, balanceOf(buyer), 1);
replaceWrapped(currency, buyer);
}
function notifyOfferEnded() public {
if (msg.sender == address(offer)){
offer = IOffer(address(0));
}
}
function replaceWrapped(address newWrapped, address oldWrappedDestination) internal {
require(isBinding());
require(wrapped.transfer(oldWrappedDestination, wrapped.balanceOf(address(this))));
wrapped = IERC20(newWrapped);
deactivate(wrapped.balanceOf(address(this)) / totalSupply());
}
function migrate() public {
address successor = msg.sender;
require(!offerExists());
require(balanceOf(successor) * 10000 >= totalSupply() * MIGRATION_QUORUM, "quorum");
replaceWrapped(successor, successor);
emit MigrationSucceeded(successor);
}
function _beforeTokenTransfer(address from, address to, uint256 amount) override internal {
if (offerExists()) {
offer.notifyMoved(from, to, amount);
}
}
function offerExists() internal view returns (bool) {
return address(offer) != address(0);
}
}
abstract contract IRecoveryDisabler {
function setRecoverable(bool enabled) public virtual;
}
abstract contract IBurnable {
function burn(uint256) virtual public;
}
abstract contract IOffer {
function isWellFunded() virtual public returns (bool);
function contest(address newOffer) virtual public;
function notifyMoved(address from, address to, uint256 value) virtual public;
}
abstract contract IOfferFactory {
function create(bytes32 salt, address buyer, uint256 pricePerShare, address currency, uint256 quorum, uint256 votePeriod) virtual public payable returns (address);
}
文件 4 的 6:ERC20Recoverable.sol
pragma solidity >=0.8;
import "./ERC20.sol";
import "./IERC20.sol";
abstract contract ERC20Recoverable is ERC20 {
struct Claim {
address claimant;
uint256 collateral;
uint256 timestamp;
address currencyUsed;
}
uint256 public constant claimPeriod = 180 days;
mapping(address => Claim) public claims;
mapping(address => bool) public recoveryDisabled;
address public customCollateralAddress;
uint256 public customCollateralRate;
function getCollateralRate(address collateralType) public virtual view returns (uint256) {
if (collateralType == address(this)) {
return 1;
} else if (collateralType == customCollateralAddress) {
return customCollateralRate;
} else {
return 0;
}
}
function _setCustomClaimCollateral(address collateral, uint256 rate) internal {
customCollateralAddress = collateral;
if (customCollateralAddress == address(0)) {
customCollateralRate = 0;
} else {
require(rate > 0, "zero");
customCollateralRate = rate;
}
emit CustomClaimCollateralChanged(collateral, rate);
}
function getClaimDeleter() virtual public view returns (address);
function setRecoverable(bool enabled) public {
recoveryDisabled[msg.sender] = !enabled;
}
function isRecoveryEnabled(address target) public view returns (bool) {
return !recoveryDisabled[target];
}
event ClaimMade(address indexed lostAddress, address indexed claimant, uint256 balance);
event ClaimCleared(address indexed lostAddress, uint256 collateral);
event ClaimDeleted(address indexed lostAddress, address indexed claimant, uint256 collateral);
event ClaimResolved(address indexed lostAddress, address indexed claimant, uint256 collateral);
event CustomClaimCollateralChanged(address newCustomCollateralAddress, uint256 newCustomCollareralRate);
function declareLost(address collateralType, address lostAddress) public {
require(isRecoveryEnabled(lostAddress), "disabled");
uint256 collateralRate = getCollateralRate(collateralType);
require(collateralRate > 0, "bad collateral");
address claimant = msg.sender;
uint256 balance = balanceOf(lostAddress);
uint256 collateral = balance * collateralRate;
IERC20 currency = IERC20(collateralType);
require(balance > 0, "empty");
require(claims[lostAddress].collateral == 0, "already claimed");
require(currency.transferFrom(claimant, address(this), collateral));
claims[lostAddress] = Claim({
claimant: claimant,
collateral: collateral,
timestamp: block.timestamp,
currencyUsed: collateralType
});
emit ClaimMade(lostAddress, claimant, balance);
}
function getClaimant(address lostAddress) public view returns (address) {
return claims[lostAddress].claimant;
}
function getCollateral(address lostAddress) public view returns (uint256) {
return claims[lostAddress].collateral;
}
function getCollateralType(address lostAddress) public view returns (address) {
return claims[lostAddress].currencyUsed;
}
function getTimeStamp(address lostAddress) public view returns (uint256) {
return claims[lostAddress].timestamp;
}
function transfer(address recipient, uint256 amount) override virtual public returns (bool) {
require(super.transfer(recipient, amount));
clearClaim();
return true;
}
function clearClaim() public {
if (claims[msg.sender].collateral != 0) {
uint256 collateral = claims[msg.sender].collateral;
IERC20 currency = IERC20(claims[msg.sender].currencyUsed);
delete claims[msg.sender];
require(currency.transfer(msg.sender, collateral));
emit ClaimCleared(msg.sender, collateral);
}
}
function recover(address lostAddress) public {
Claim memory claim = claims[lostAddress];
uint256 collateral = claim.collateral;
IERC20 currency = IERC20(claim.currencyUsed);
require(collateral != 0, "not found");
require(claim.claimant == msg.sender, "not claimant");
require(claim.timestamp + claimPeriod <= block.timestamp, "too early");
address claimant = claim.claimant;
delete claims[lostAddress];
require(currency.transfer(claimant, collateral));
_transfer(lostAddress, claimant, balanceOf(lostAddress));
emit ClaimResolved(lostAddress, claimant, collateral);
}
function deleteClaim(address lostAddress) public {
require(msg.sender == getClaimDeleter(), "no access");
Claim memory claim = claims[lostAddress];
IERC20 currency = IERC20(claim.currencyUsed);
require(claim.collateral != 0, "not found");
delete claims[lostAddress];
require(currency.transfer(claim.claimant, claim.collateral));
emit ClaimDeleted(lostAddress, claim.claimant, claim.collateral);
}
}
文件 5 的 6:IERC20.sol
pragma solidity >=0.8;
interface IERC20 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
文件 6 的 6:IERC677Receiver.sol
pragma solidity >=0.8;
interface IERC677Receiver {
function onTokenTransfer(address from, uint256 amount, bytes calldata data) external returns (bool);
}
{
"compilationTarget": {
"DraggableShares.sol": "DraggableShares"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"string","name":"_terms","type":"string"},{"internalType":"address","name":"wrappedToken","type":"address"},{"internalType":"uint256","name":"quorumBps","type":"uint256"},{"internalType":"uint256","name":"votePeriodSeconds","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"lostAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"collateral","type":"uint256"}],"name":"ClaimCleared","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"lostAddress","type":"address"},{"indexed":true,"internalType":"address","name":"claimant","type":"address"},{"indexed":false,"internalType":"uint256","name":"collateral","type":"uint256"}],"name":"ClaimDeleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"lostAddress","type":"address"},{"indexed":true,"internalType":"address","name":"claimant","type":"address"},{"indexed":false,"internalType":"uint256","name":"balance","type":"uint256"}],"name":"ClaimMade","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"lostAddress","type":"address"},{"indexed":true,"internalType":"address","name":"claimant","type":"address"},{"indexed":false,"internalType":"uint256","name":"collateral","type":"uint256"}],"name":"ClaimResolved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newCustomCollateralAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"newCustomCollareralRate","type":"uint256"}],"name":"CustomClaimCollateralChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newContractAddress","type":"address"}],"name":"MigrationSucceeded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"claims","outputs":[{"internalType":"address","name":"claimant","type":"address"},{"internalType":"uint256","name":"collateral","type":"uint256"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"address","name":"currencyUsed","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"clearClaim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"customCollateralAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"customCollateralRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"collateralType","type":"address"},{"internalType":"address","name":"lostAddress","type":"address"}],"name":"declareLost","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"lostAddress","type":"address"}],"name":"deleteClaim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"disableRecovery","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"buyer","type":"address"},{"internalType":"address","name":"currency","type":"address"}],"name":"drag","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"contract IOfferFactory","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getClaimDeleter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"lostAddress","type":"address"}],"name":"getClaimant","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"lostAddress","type":"address"}],"name":"getCollateral","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"collateralType","type":"address"}],"name":"getCollateralRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"lostAddress","type":"address"}],"name":"getCollateralType","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"lostAddress","type":"address"}],"name":"getTimeStamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isBinding","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"isRecoveryEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"uint256","name":"pricePerShare","type":"uint256"},{"internalType":"address","name":"currency","type":"address"}],"name":"makeAcquisitionOffer","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"migrate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"notifyOfferEnded","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"offer","outputs":[{"internalType":"contract IOffer","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onTokenTransfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"quorum","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"lostAddress","type":"address"}],"name":"recover","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"recoveryDisabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"enabled","type":"bool"}],"name":"setRecoverable","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"terms","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"transferAndCall","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"unwrap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unwrapConversionFactor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"votePeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"shareholder","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"wrap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"wrapped","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"}]