Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
6522f12
add synthetixdebtshare
dbeal-eth Nov 11, 2021
510823f
lots of implementation
dbeal-eth Nov 13, 2021
8ed2fff
tests work
dbeal-eth Nov 15, 2021
834ec9d
add synthetix debt share tests
dbeal-eth Nov 16, 2021
bff2fb6
fix unit test failures
dbeal-eth Nov 16, 2021
9043de1
remove comments and np
dbeal-eth Nov 16, 2021
ae5c111
fix lints
dbeal-eth Nov 16, 2021
d532380
initial period is now 1
dbeal-eth Nov 16, 2021
3b53405
add a few tests for coverage
dbeal-eth Nov 16, 2021
5076ab1
use `hardhat-interact` instead of builtin
dbeal-eth Nov 28, 2021
e351a65
fix module resolution
dbeal-eth Nov 29, 2021
f1280ab
fix package lock again
dbeal-eth Nov 29, 2021
fea5c91
fix provider url issues and network id for kvoan
dbeal-eth Nov 29, 2021
10ea7bd
add synthetixdebtshare
dbeal-eth Nov 11, 2021
7cf745b
lots of implementation
dbeal-eth Nov 13, 2021
113ada3
tests work
dbeal-eth Nov 15, 2021
f267e6e
add synthetix debt share tests
dbeal-eth Nov 16, 2021
667fcc8
fix unit test failures
dbeal-eth Nov 16, 2021
2265bf6
remove comments and np
dbeal-eth Nov 16, 2021
fb1530a
fix lints
dbeal-eth Nov 16, 2021
bdf3e69
initial period is now 1
dbeal-eth Nov 16, 2021
87cd0da
add a few tests for coverage
dbeal-eth Nov 16, 2021
8171cb3
now its not necessary to work around load-contracts functionality wit…
dbeal-eth Dec 2, 2021
1deac59
Merge branch 'sip-185-debt-shares' of https://github.com/Synthetixio/…
dbeal-eth Dec 12, 2021
e961d51
oops
dbeal-eth Dec 12, 2021
09c501e
add migration script start
dbeal-eth Dec 12, 2021
54bc7b2
Merge branch 'develop' into sip-185-debt-shares
barrasso Jan 11, 2022
26bddc0
remove unnecessary migration contracts
barrasso Jan 11, 2022
093812a
Update package-lock.json
barrasso Jan 11, 2022
7b052d8
fix SynthetixDebtShare allowance tests and update interface
barrasso Jan 12, 2022
074f973
fix remaining unit tests
barrasso Jan 13, 2022
3c10f8d
Merge branch 'develop' into sip-185-debt-shares
barrasso Jan 13, 2022
f70d5e3
Merge branch 'develop' into sip-185-debt-shares
barrasso Jan 13, 2022
9005af7
Merge branch 'sip-185-debt-shares' of https://github.com/Synthetixio/…
barrasso Jan 13, 2022
a4e67b9
final fixes
dbeal-eth Jan 13, 2022
7d91e8a
Merge branch 'develop' into sip-185-debt-shares
dbeal-eth Jan 14, 2022
832fe23
Merge branch 'develop' into sip-185-debt-shares
dbeal-eth Jan 15, 2022
1b12e4c
add apparently missing tests for importAddresses
dbeal-eth Jan 15, 2022
3783e05
fixes for reccomendations from @artdgn
dbeal-eth Jan 19, 2022
6104a06
more small cleanups
dbeal-eth Jan 19, 2022
1c8d00c
better balance history scanning
dbeal-eth Jan 19, 2022
da67a27
update docs
dbeal-eth Jan 19, 2022
21e76b9
Merge branch 'develop' into sip-185-debt-shares
barrasso Jan 20, 2022
63dc62b
minor formatting fix
barrasso Jan 20, 2022
d8aeb25
remove unneeded migration contracts
barrasso Jan 20, 2022
9e35b2b
fix lint error
barrasso Jan 20, 2022
f3537b9
Merge branch 'develop' into sip-185-debt-shares
barrasso Jan 21, 2022
afdefac
Merge remote-tracking branch 'origin/develop' into sip-185-debt-shares
dbeal-eth Jan 24, 2022
49d8a69
fixes for comments from audit
dbeal-eth Jan 24, 2022
dc3c276
add requirements for v3
dbeal-eth Jan 27, 2022
804a96b
Merge remote-tracking branch 'origin/develop' into sip-185-debt-shares
dbeal-eth Jan 27, 2022
19eb553
final audit notes
dbeal-eth Feb 1, 2022
331cb88
Merge remote-tracking branch 'origin/develop' into sip-185-debt-shares
dbeal-eth Feb 1, 2022
82c0c70
add support for complete control of altering balance during initial p…
dbeal-eth Feb 3, 2022
43b6fe1
fix lint
dbeal-eth Feb 3, 2022
fe3e180
Merge remote-tracking branch 'origin/develop' into sip-185-debt-shares
dbeal-eth Feb 3, 2022
ebdb8b0
fix tests... again?
dbeal-eth Feb 3, 2022
86bde07
fix scripts and stuff
dbeal-eth Feb 3, 2022
1053059
remove legacy contracts
dbeal-eth Feb 3, 2022
d0c69d5
fix integration tests && remove unnecessarsy changes
dbeal-eth Feb 3, 2022
b9add27
fix integration caught bug for `importFeePeriod`
dbeal-eth Feb 4, 2022
15202c1
undo accidental changes to config.json
dbeal-eth Feb 4, 2022
b8e0e9a
fix deploy tests
dbeal-eth Feb 4, 2022
dd7961b
fix task node
dbeal-eth Feb 4, 2022
9b54331
remove unnecessary conditional
dbeal-eth Feb 4, 2022
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
22 changes: 7 additions & 15 deletions contracts/BaseSynthetix.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import "./interfaces/ISynthetix.sol";
// Internal references
import "./interfaces/ISynth.sol";
import "./TokenState.sol";
import "./interfaces/ISynthetixState.sol";
import "./interfaces/ISystemStatus.sol";
import "./interfaces/IExchanger.sol";
import "./interfaces/IIssuer.sol";
Expand All @@ -26,7 +25,6 @@ contract BaseSynthetix is IERC20, ExternStateToken, MixinResolver, ISynthetix {
bytes32 public constant sUSD = "sUSD";

// ========== ADDRESS RESOLVER CONFIGURATION ==========
bytes32 private constant CONTRACT_SYNTHETIXSTATE = "SynthetixState";
bytes32 private constant CONTRACT_SYSTEMSTATUS = "SystemStatus";
bytes32 private constant CONTRACT_EXCHANGER = "Exchanger";
bytes32 private constant CONTRACT_ISSUER = "Issuer";
Expand All @@ -50,16 +48,11 @@ contract BaseSynthetix is IERC20, ExternStateToken, MixinResolver, ISynthetix {

// Note: use public visibility so that it can be invoked in a subclass
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
addresses = new bytes32[](5);
addresses[0] = CONTRACT_SYNTHETIXSTATE;
addresses[1] = CONTRACT_SYSTEMSTATUS;
addresses[2] = CONTRACT_EXCHANGER;
addresses[3] = CONTRACT_ISSUER;
addresses[4] = CONTRACT_REWARDSDISTRIBUTION;
}

function synthetixState() internal view returns (ISynthetixState) {
return ISynthetixState(requireAndGetAddress(CONTRACT_SYNTHETIXSTATE));
addresses = new bytes32[](4);
addresses[0] = CONTRACT_SYSTEMSTATUS;
addresses[1] = CONTRACT_EXCHANGER;
addresses[2] = CONTRACT_ISSUER;
addresses[3] = CONTRACT_REWARDSDISTRIBUTION;
}

function systemStatus() internal view returns (ISystemStatus) {
Expand Down Expand Up @@ -147,14 +140,13 @@ contract BaseSynthetix is IERC20, ExternStateToken, MixinResolver, ISynthetix {
}

function _canTransfer(address account, uint value) internal view returns (bool) {
(uint initialDebtOwnership, ) = synthetixState().issuanceData(account);

if (initialDebtOwnership > 0) {
if (issuer().debtBalanceOf(account, sUSD) > 0) {
(uint transferable, bool anyRateIsInvalid) =
issuer().transferableSynthetixAndAnyRateIsInvalid(account, tokenState.balanceOf(account));
require(value <= transferable, "Cannot transfer staked or escrowed SNX");
require(!anyRateIsInvalid, "A synth or SNX rate is invalid");
}

return true;
}

Expand Down
227 changes: 51 additions & 176 deletions contracts/FeePool.sol

Large diffs are not rendered by default.

170 changes: 54 additions & 116 deletions contracts/Issuer.sol

Large diffs are not rendered by default.

302 changes: 302 additions & 0 deletions contracts/SynthetixDebtShare.sol
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;

/**
* 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;
}

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

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 {
isInitialized = true;
}

/* ========== INTERNAL FUNCTIONS ======== */
function _increaseBalance(address account, uint amount) internal {
uint accountBalanceCount = balances[account].length;

if (accountBalanceCount == 0) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code doesn't work correctly because, in the case that the else case is taken, the lastBalance is not taken into account from prevPeriodId

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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