-
Notifications
You must be signed in to change notification settings - Fork 610
Sip 185 debt shares #1601
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
Sip 185 debt shares #1601
Changes from all commits
6522f12
510823f
8ed2fff
834ec9d
bff2fb6
9043de1
ae5c111
d532380
3b53405
5076ab1
e351a65
f1280ab
fea5c91
10ea7bd
7cf745b
113ada3
f267e6e
667fcc8
2265bf6
fb1530a
bdf3e69
87cd0da
8171cb3
1deac59
e961d51
09c501e
54bc7b2
26bddc0
093812a
7b052d8
074f973
3c10f8d
f70d5e3
9005af7
a4e67b9
7d91e8a
832fe23
1b12e4c
3783e05
6104a06
1c8d00c
da67a27
21e76b9
63dc62b
d8aeb25
9e35b2b
f3537b9
afdefac
49d8a69
dc3c276
804a96b
19eb553
331cb88
82c0c70
43b6fe1
fe3e180
ebdb8b0
86bde07
1053059
d0c69d5
b9add27
15202c1
b8e0e9a
dd7961b
9b54331
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,302 @@ | ||
| pragma solidity ^0.5.16; | ||
|
|
||
| // Inheritance | ||
| import "./Owned.sol"; | ||
| import "./interfaces/ISynthetixDebtShare.sol"; | ||
| import "./MixinResolver.sol"; | ||
|
|
||
| // Libraries | ||
| import "./SafeDecimalMath.sol"; | ||
|
|
||
| // https://docs.synthetix.io/contracts/source/contracts/synthetixdebtshare | ||
| contract SynthetixDebtShare is Owned, MixinResolver, ISynthetixDebtShare { | ||
| using SafeMath for uint; | ||
| using SafeDecimalMath for uint; | ||
|
|
||
| struct PeriodBalance { | ||
| uint128 amount; | ||
| uint128 periodId; | ||
| } | ||
|
|
||
| bytes32 public constant CONTRACT_NAME = "SynthetixDebtShare"; | ||
|
|
||
| bytes32 private constant CONTRACT_ISSUER = "Issuer"; | ||
|
|
||
| uint internal constant MAX_PERIOD_ITERATE = 30; | ||
|
|
||
| /* ========== STATE VARIABLES ========== */ | ||
|
|
||
| /** | ||
| * Addresses selected by owner which are allowed to call `transferFrom` to manage debt shares | ||
| */ | ||
| mapping(address => bool) public authorizedBrokers; | ||
|
|
||
| /** | ||
| * Addresses selected by owner which are allowed to call `takeSnapshot` | ||
| * `takeSnapshot` is not public because only a small number of snapshots can be retained for a period of time, and so they | ||
| * must be controlled to prevent censorship | ||
| */ | ||
| mapping(address => bool) public authorizedToSnapshot; | ||
|
|
||
| /** | ||
| * Records a user's balance as it changes from period to period. | ||
| * The last item in the array always represents the user's most recent balance | ||
| * The intermediate balance is only recorded if | ||
| * `currentPeriodId` differs (which would happen upon a call to `setCurrentPeriodId`) | ||
| */ | ||
| mapping(address => PeriodBalance[]) public balances; | ||
dbeal-eth marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * Records totalSupply as it changes from period to period | ||
| * Similar to `balances`, the `totalSupplyOnPeriod` at index `currentPeriodId` matches the current total supply | ||
| * Any other period ID would represent its most recent totalSupply before the period ID changed. | ||
| */ | ||
| mapping(uint => uint) public totalSupplyOnPeriod; | ||
|
|
||
|
|
||
| /* ERC20 fields. */ | ||
| string public name; | ||
| string public symbol; | ||
| uint8 public decimals; | ||
|
|
||
| /** | ||
| * Period ID used for recording accounting changes | ||
| * Can only increment | ||
| */ | ||
| uint128 public currentPeriodId; | ||
|
|
||
| /** | ||
| * Prevents the owner from making further changes to debt shares after initial import | ||
| */ | ||
| bool public isInitialized = false; | ||
|
|
||
| constructor(address _owner, address _resolver) public Owned(_owner) MixinResolver(_resolver) { | ||
| name = "Synthetix Debt Shares"; | ||
| symbol = "SDS"; | ||
| decimals = 18; | ||
|
|
||
| // NOTE: must match initial fee period ID on `FeePool` constructor if issuer wont report | ||
| currentPeriodId = 1; | ||
| } | ||
| function resolverAddressesRequired() public view returns (bytes32[] memory addresses) { | ||
| addresses = new bytes32[](1); | ||
| addresses[0] = CONTRACT_ISSUER; | ||
| } | ||
|
|
||
| /* ========== VIEWS ========== */ | ||
|
|
||
| function balanceOf(address account) public view returns (uint) { | ||
| uint accountPeriodHistoryCount = balances[account].length; | ||
|
|
||
| if (accountPeriodHistoryCount == 0) { | ||
| return 0; | ||
| } | ||
|
|
||
| return uint(balances[account][accountPeriodHistoryCount - 1].amount); | ||
| } | ||
|
|
||
| function balanceOfOnPeriod(address account, uint periodId) public view returns (uint) { | ||
| uint accountPeriodHistoryCount = balances[account].length; | ||
|
|
||
| int oldestHistoryIterate = int(MAX_PERIOD_ITERATE < accountPeriodHistoryCount ? accountPeriodHistoryCount - MAX_PERIOD_ITERATE : 0); | ||
| int i; | ||
| for (i = int(accountPeriodHistoryCount) - 1;i >= oldestHistoryIterate;i--) { | ||
| if (balances[account][uint(i)].periodId <= periodId) { | ||
| return uint(balances[account][uint(i)].amount); | ||
| } | ||
| } | ||
|
|
||
| require(i < 0, "SynthetixDebtShare: not found in recent history"); | ||
| return 0; | ||
dbeal-eth marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| function totalSupply() public view returns (uint) { | ||
| return totalSupplyOnPeriod[currentPeriodId]; | ||
| } | ||
|
|
||
| function sharePercent(address account) external view returns (uint) { | ||
| return sharePercentOnPeriod(account, currentPeriodId); | ||
| } | ||
|
|
||
| function sharePercentOnPeriod(address account, uint periodId) public view returns (uint) { | ||
dbeal-eth marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| uint balance = balanceOfOnPeriod(account, periodId); | ||
|
|
||
| if (balance == 0) { | ||
| return 0; | ||
| } | ||
|
|
||
| return balance.divideDecimal(totalSupplyOnPeriod[periodId]); | ||
| } | ||
|
|
||
| function allowance(address, address spender) public view returns (uint) { | ||
| if (authorizedBrokers[spender]) { | ||
| return uint(-1); | ||
dbeal-eth marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| else { | ||
| return 0; | ||
| } | ||
| } | ||
|
|
||
| /* ========== MUTATIVE FUNCTIONS ========== */ | ||
|
|
||
| function addAuthorizedBroker(address target) external onlyOwner { | ||
| authorizedBrokers[target] = true; | ||
| emit ChangeAuthorizedBroker(target, true); | ||
| } | ||
|
|
||
| function removeAuthorizedBroker(address target) external onlyOwner { | ||
| authorizedBrokers[target] = false; | ||
| emit ChangeAuthorizedBroker(target, false); | ||
| } | ||
|
|
||
| function addAuthorizedToSnapshot(address target) external onlyOwner { | ||
| authorizedToSnapshot[target] = true; | ||
| emit ChangeAuthorizedToSnapshot(target, true); | ||
| } | ||
|
|
||
| function removeAuthorizedToSnapshot(address target) external onlyOwner { | ||
| authorizedToSnapshot[target] = false; | ||
| emit ChangeAuthorizedToSnapshot(target, false); | ||
| } | ||
|
|
||
| function takeSnapshot(uint128 id) external onlyAuthorizedToSnapshot { | ||
| require(id > currentPeriodId, "period id must always increase"); | ||
| totalSupplyOnPeriod[id] = totalSupplyOnPeriod[currentPeriodId]; | ||
| currentPeriodId = id; | ||
| } | ||
|
|
||
| function mintShare(address account, uint256 amount) external onlyIssuer { | ||
| require(account != address(0), "ERC20: mint to the zero address"); | ||
|
|
||
| _increaseBalance(account, amount); | ||
|
|
||
| totalSupplyOnPeriod[currentPeriodId] = totalSupplyOnPeriod[currentPeriodId].add(amount); | ||
|
|
||
| emit Transfer(address(0), account, amount); | ||
| emit Mint(account, amount); | ||
| } | ||
|
|
||
| function burnShare(address account, uint256 amount) external onlyIssuer { | ||
| require(account != address(0), "ERC20: burn from zero address"); | ||
|
|
||
| _deductBalance(account, amount); | ||
|
|
||
| totalSupplyOnPeriod[currentPeriodId] = totalSupplyOnPeriod[currentPeriodId].sub(amount); | ||
| emit Transfer(account, address(0), amount); | ||
| emit Burn(account, amount); | ||
| } | ||
|
|
||
| function approve(address, uint256) external pure returns(bool) { | ||
| revert("debt shares are not transferrable"); | ||
| } | ||
|
|
||
| function transfer(address, uint256) external pure returns(bool) { | ||
| revert("debt shares are not transferrable"); | ||
| } | ||
|
|
||
| function transferFrom(address from, address to, uint256 amount) external onlyAuthorizedBrokers returns(bool) { | ||
| require(to != address(0), "ERC20: send to the zero address"); | ||
|
|
||
| _deductBalance(from, amount); | ||
| _increaseBalance(to, amount); | ||
|
|
||
| emit Transfer(address(from), address(to), amount); | ||
dbeal-eth marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| return true; | ||
| } | ||
|
|
||
| function importAddresses(address[] calldata accounts, uint256[] calldata amounts) external onlyOwner onlySetup { | ||
| uint supply = totalSupplyOnPeriod[currentPeriodId]; | ||
|
|
||
| for (uint i = 0; i < accounts.length; i++) { | ||
| uint curBalance = balanceOf(accounts[i]); | ||
| if (curBalance < amounts[i]) { | ||
| uint amount = amounts[i] - curBalance; | ||
| _increaseBalance(accounts[i], amount); | ||
| supply = supply.add(amount); | ||
| emit Mint(accounts[i], amount); | ||
| emit Transfer(address(0), accounts[i], amount); | ||
| } | ||
| else if (curBalance > amounts[i]) { | ||
| uint amount = curBalance - amounts[i]; | ||
| _deductBalance(accounts[i], amount); | ||
| supply = supply.sub(amount); | ||
| emit Burn(accounts[i], amount); | ||
| emit Transfer(accounts[i], address(0), amount); | ||
| } | ||
| } | ||
|
|
||
| totalSupplyOnPeriod[currentPeriodId] = supply; | ||
| } | ||
|
|
||
| function finishSetup() external onlyOwner { | ||
dbeal-eth marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| isInitialized = true; | ||
| } | ||
|
|
||
| /* ========== INTERNAL FUNCTIONS ======== */ | ||
| function _increaseBalance(address account, uint amount) internal { | ||
| uint accountBalanceCount = balances[account].length; | ||
|
|
||
| if (accountBalanceCount == 0) { | ||
|
Contributor
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. nit: this code block can be restructured to possibly be more readable: uint prevPeriodId = accountBalanceCount > 0 ? balances[account][accountBalanceCount - 1].periodId : 0;
if (currentPeriodId == prevPeriodId) {
// add to last entry
uint lastBalance = balances[account][accountBalanceCount - 1].amount;
balances[account][accountBalanceCount - 1].amount = lastBalance.add(amount);
} else {
// push new entry
balances[account].push(PeriodBalance(amount, currentPeriodId));
}
Contributor
Author
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. this code doesn't work correctly because, in the case that the
Contributor
Author
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. i understand that your concern is that this code is too hard to parse, so I will see if there is anything that can be done to reduce the comprehension barrier |
||
| balances[account].push(PeriodBalance(uint128(amount), uint128(currentPeriodId))); | ||
| } | ||
| else { | ||
| uint128 newAmount = uint128(uint(balances[account][accountBalanceCount - 1].amount).add(amount)); | ||
|
|
||
| if (balances[account][accountBalanceCount - 1].periodId != currentPeriodId) { | ||
| balances[account].push(PeriodBalance(newAmount, currentPeriodId)); | ||
| } | ||
| else { | ||
| balances[account][accountBalanceCount - 1].amount = newAmount; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| function _deductBalance(address account, uint amount) internal { | ||
| uint accountBalanceCount = balances[account].length; | ||
|
|
||
| require(accountBalanceCount != 0, "SynthetixDebtShare: account has no share to deduct"); | ||
|
|
||
| uint128 newAmount = uint128(uint(balances[account][accountBalanceCount - 1].amount).sub(amount)); | ||
|
|
||
| if (balances[account][accountBalanceCount - 1].periodId != currentPeriodId) { | ||
| balances[account].push(PeriodBalance( | ||
| newAmount, | ||
| currentPeriodId | ||
| )); | ||
| } | ||
| else { | ||
| balances[account][accountBalanceCount - 1].amount = newAmount; | ||
| } | ||
| } | ||
|
|
||
| /* ========== MODIFIERS ========== */ | ||
|
|
||
| modifier onlyIssuer() { | ||
| require(msg.sender == requireAndGetAddress(CONTRACT_ISSUER), "SynthetixDebtShare: only issuer can mint/burn"); | ||
| _; | ||
| } | ||
|
|
||
| modifier onlyAuthorizedToSnapshot() { | ||
| require(authorizedToSnapshot[msg.sender] || msg.sender == requireAndGetAddress(CONTRACT_ISSUER), "SynthetixDebtShare: not authorized to snapshot"); | ||
| _; | ||
| } | ||
|
|
||
| modifier onlyAuthorizedBrokers() { | ||
| require(authorizedBrokers[msg.sender], "SynthetixDebtShare: only brokers can transferFrom"); | ||
| _; | ||
| } | ||
|
|
||
| modifier onlySetup() { | ||
| require(!isInitialized, "SynthetixDebt: only callable while still initializing"); | ||
| _; | ||
| } | ||
|
|
||
| /* ========== EVENTS ========== */ | ||
| event Mint(address indexed account, uint amount); | ||
| event Burn(address indexed account, uint amount); | ||
| event Transfer(address indexed from, address indexed to, uint value); | ||
|
|
||
| event ChangeAuthorizedBroker(address indexed authorizedBroker, bool authorized); | ||
| event ChangeAuthorizedToSnapshot(address indexed authorizedToSnapshot, bool authorized); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.