-
Notifications
You must be signed in to change notification settings - Fork 0
Initial security pools #2
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
base: main
Are you sure you want to change the base?
Changes from all commits
7750bf3
9303526
bec70b9
e6b84ed
f50465f
7aa871a
2538fa1
70e892a
62045b0
4771f4e
a167feb
59e4840
0192393
15e4dd8
f507a4e
50d1e76
6a2dd53
032e94e
39ac728
eb3e269
2c5924b
42af027
4da36b4
f20b02f
eed4dc0
82016a5
c441c35
1822236
1bcc340
b2f9e51
3fba083
c558998
1e7e0b1
5fc54e8
6e4c6ce
dbbc53b
00557e4
452a032
82fff84
27d63b2
d6cce99
f722b71
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| // SPDX-License-Identifier: UNICENSE | ||
| pragma solidity 0.8.30; | ||
| uint256 constant AUCTION_TIME = 1 weeks; | ||
|
|
||
| contract Auction { | ||
| mapping(address => uint256) public purchasedRep; | ||
| uint256 public totalRepPurchased; | ||
| uint256 public repAvailable; | ||
| uint256 public auctionStarted; | ||
| uint256 public ethAmountToBuy; | ||
| bool public finalized; | ||
| address immutable owner; | ||
|
|
||
| event Participated(address user, uint256 repAmount, uint256 ethAmount, uint256 totalRepPurchased); | ||
| event FinalizedAuction(address user, uint256 repAmount, uint256 ethAmount); | ||
| event AuctionStarted(uint256 ethAmountToBuy, uint256 repAvailable); | ||
|
|
||
| constructor(address _owner) { | ||
| owner = _owner; | ||
| } | ||
|
|
||
| function participate(uint256 repToBuy) public payable { | ||
| require(auctionStarted > 0, 'Auction needs to have started'); | ||
| require(!finalized, 'Already finalized'); | ||
| require(msg.value > 0, 'need to invest with eth!'); | ||
| require(address(this).balance <= ethAmountToBuy, 'attempting to overfund'); | ||
| require(totalRepPurchased + repToBuy <= repAvailable, 'attempt to buy too much rep'); | ||
| purchasedRep[msg.sender] = repToBuy; // todo, currently anyone can buy with any price | ||
|
Member
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. For prototyping, how hard would it be to just have a linear countdown since start and just require the price matches exactly? With Interceptor testing we can make this exact I think, since we can control how many blocks after auction start block the participate function is called at. |
||
| totalRepPurchased += repToBuy; | ||
| emit Participated(msg.sender, repToBuy, msg.value, totalRepPurchased); | ||
| } | ||
|
|
||
| function startAuction(uint256 _ethAmountToBuy, uint256 _repAvailable) public { | ||
| require(auctionStarted == 0, 'Already started!'); | ||
| auctionStarted = block.timestamp; | ||
| ethAmountToBuy = _ethAmountToBuy; | ||
| repAvailable = _repAvailable; | ||
| emit AuctionStarted(ethAmountToBuy, repAvailable); | ||
| } | ||
|
|
||
| function finalizeAuction() public { | ||
| //require(block.timestamp > auctionStarted + AUCTION_TIME, 'Auction needs to have ended first'); // caller checks | ||
|
Member
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. Why have the caller check instead of putting the check here? I also think there is value in checking twice in situations like this until the final optimization stage of development. Better to play it safe until the last minute when we are gas optimizing. |
||
| require(msg.sender == owner, 'Only owner can finalize'); | ||
| require(!finalized, 'Already finalized'); | ||
| finalized = true; | ||
| (bool sent, ) = payable(owner).call{value: address(this).balance}(''); | ||
|
Member
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. Solidity still doesn't have a cleaner way to send ETH than this? 😢 |
||
| require(sent, 'Failed to send Ether'); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| // SPDX-License-Identifier: UNICENSE | ||
| pragma solidity 0.8.30; | ||
|
|
||
| import '../ERC20.sol'; | ||
|
|
||
| contract CompleteSet is ERC20 { | ||
|
|
||
| address public immutable securityPool; | ||
|
|
||
| constructor(address _securityPool) ERC20('CompleteSet', 'CS') { | ||
| securityPool = _securityPool; | ||
| } | ||
|
|
||
| function mint(address account, uint256 value) external { | ||
| require(msg.sender == securityPool, 'Not securityPool'); | ||
| _mint(account, value); | ||
| } | ||
|
|
||
| function burn(address account, uint256 value) external { | ||
| require(msg.sender == securityPool, 'Not securityPool'); | ||
| _burn(account, value); | ||
| } | ||
|
|
||
| function splitSomehow() external { | ||
| // we need to somehow split this in a way that balances are maintained | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,152 @@ | ||||||
| // SPDX-License-Identifier: UNICENSE | ||||||
| pragma solidity 0.8.30; | ||||||
|
|
||||||
| import { IWeth9 } from './interfaces/IWeth9.sol'; | ||||||
| import { OpenOracle } from './openOracle/OpenOracle.sol'; | ||||||
| import { ReputationToken } from '../ReputationToken.sol'; | ||||||
| import { ISecurityPool } from './interfaces/ISecurityPool.sol'; | ||||||
|
|
||||||
| // price oracle | ||||||
| uint256 constant PRICE_VALID_FOR_SECONDS = 1 hours; | ||||||
|
|
||||||
| enum OperationType { | ||||||
| Liquidation, | ||||||
| WithdrawRep, | ||||||
| SetSecurityBondsAllowance | ||||||
| } | ||||||
|
|
||||||
| uint256 constant gasConsumedOpenOracleReportPrice = 100000; //TODO | ||||||
| uint32 constant gasConsumedSettlement = 1000000; //TODO | ||||||
|
Comment on lines
+18
to
+19
Member
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 feel like these should be variables that are automatically updated each time these functions are called, so when gas price changes occur we correctly follow them. |
||||||
|
|
||||||
| IWeth9 constant WETH = IWeth9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); | ||||||
|
|
||||||
|
|
||||||
| struct QueuedOperation { | ||||||
| OperationType operation; | ||||||
| address initiatorVault; | ||||||
| address targetVault; | ||||||
| uint256 amount; | ||||||
| } | ||||||
|
|
||||||
| contract PriceOracleManagerAndOperatorQueuer { | ||||||
| uint256 public pendingReportId; | ||||||
| uint256 public queuedPendingOperationId; | ||||||
| uint256 public lastSettlementTimestamp; | ||||||
| uint256 public lastPrice; // (REP * PRICE_PRECISION) / ETH; | ||||||
| ReputationToken immutable reputationToken; | ||||||
| ISecurityPool public immutable securityPool; | ||||||
| OpenOracle public immutable openOracle; | ||||||
|
|
||||||
| event PriceReported(uint256 reportId, uint256 price); | ||||||
| event ExecutetedQueuedOperation(uint256 operationId, OperationType operation, bool success); | ||||||
|
|
||||||
| // operation queuing | ||||||
| uint256 public previousQueuedOperationId; | ||||||
| mapping(uint256 => QueuedOperation) public queuedOperations; | ||||||
|
|
||||||
| constructor(OpenOracle _openOracle, ISecurityPool _securityPool, ReputationToken _reputationToken) { | ||||||
| reputationToken = _reputationToken; | ||||||
| securityPool = _securityPool; | ||||||
| openOracle = _openOracle; | ||||||
| } | ||||||
|
|
||||||
| function setRepEthPrice(uint256 _lastPrice) public { | ||||||
| require(msg.sender == address(securityPool), 'only security pool can set'); | ||||||
| lastPrice = _lastPrice; | ||||||
| } | ||||||
|
|
||||||
| function getRequestPriceEthCost() public view returns (uint256) {// todo, probably something else | ||||||
| // https://github.com/j0i0m0b0o/openOracleBase/blob/feeTokenChange/src/OpenOracle.sol#L100 | ||||||
| uint256 ethCost = block.basefee * 4 * (gasConsumedSettlement + gasConsumedOpenOracleReportPrice); // todo, probably something else | ||||||
| return ethCost; | ||||||
| } | ||||||
| function requestPrice() public payable { | ||||||
| require(pendingReportId == 0, 'Already pending request'); | ||||||
| // https://github.com/j0i0m0b0o/openOracleBase/blob/feeTokenChange/src/OpenOracle.sol#L100 | ||||||
| uint256 ethCost = getRequestPriceEthCost();// todo, probably something else | ||||||
| require(msg.value >= ethCost, 'not big enough eth bounty'); | ||||||
|
|
||||||
| // TODO, research more on how to set these params | ||||||
| OpenOracle.CreateReportParams memory reportparams = OpenOracle.CreateReportParams({ | ||||||
| exactToken1Report: 26392439800,//block.basefee * 200 / lastPrice, // initial oracle liquidity in token1 | ||||||
|
Member
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.
Suggested change
|
||||||
| escalationHalt: reputationToken.totalSupply() / 100000, // amount of token1 past which escalation stops but disputes can still happen | ||||||
| settlerReward: block.basefee * 2 * gasConsumedOpenOracleReportPrice, // eth paid to settler in wei | ||||||
| token1Address: address(reputationToken), // address of token1 in the oracle report instance | ||||||
| settlementTime: 15 * 12,//~15 blocks // report instance can settle if no disputes within this timeframe | ||||||
|
Member
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.
Suggested change
|
||||||
| disputeDelay: 0, // time disputes must wait after every new report | ||||||
| protocolFee: 0, // fee paid to protocolFeeRecipient. 1000 = 0.01% | ||||||
| token2Address: address(WETH), // address of token2 in the oracle report instance | ||||||
| callbackGasLimit: gasConsumedSettlement, // gas the settlement callback must use | ||||||
| feePercentage: 10000, // 0.1% atm, TODO,// fee paid to previous reporter. 1000 = 0.01% | ||||||
| multiplier: 140, // amount by which newAmount1 must increase versus old amount1. 140 = 1.4x | ||||||
| timeType: true, // true for block timestamp, false for block number | ||||||
| trackDisputes: false, // true keeps a readable dispute history for smart contracts | ||||||
| keepFee: false, // true means initial reporter keeps the initial reporter reward. if false, it goes to protocolFeeRecipient | ||||||
| callbackContract: address(this), // contract address for settle to call back into | ||||||
| callbackSelector: this.openOracleReportPrice.selector, // method in the callbackContract you want called. | ||||||
| protocolFeeRecipient: address(0x0), // address that receives protocol fees and initial reporter rewards if keepFee set to false | ||||||
| feeToken: true //if true, protocol fees + fees paid to previous reporter are in tokenToSwap. if false, in not(tokenToSwap) | ||||||
| }); //typically if feeToken true, fees are paid in less valuable token, if false, fees paid in more valuable token | ||||||
|
|
||||||
| pendingReportId = openOracle.createReportInstance{value: ethCost}(reportparams); | ||||||
| } | ||||||
| function openOracleReportPrice(uint256 reportId, uint256 price, uint256, address, address) external { | ||||||
| require(msg.sender == address(openOracle), 'only open oracle can call'); | ||||||
| require(reportId == pendingReportId, 'not report created by us'); | ||||||
| pendingReportId = 0; | ||||||
| lastSettlementTimestamp = block.timestamp; | ||||||
| lastPrice = price; | ||||||
| emit PriceReported(reportId, lastPrice); | ||||||
| if (queuedPendingOperationId != 0) { // todo we maybe should allow executing couple operations? | ||||||
| executeQueuedOperation(queuedPendingOperationId); | ||||||
| queuedPendingOperationId = 0; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| function isPriceValid() public view returns (bool) { | ||||||
| return lastSettlementTimestamp + PRICE_VALID_FOR_SECONDS > block.timestamp; | ||||||
| } | ||||||
|
|
||||||
| function requestPriceIfNeededAndQueueOperation(OperationType operation, address targetVault, uint256 amount) public payable { | ||||||
| require(amount > 0, 'need to do non zero operation'); | ||||||
| previousQueuedOperationId++; | ||||||
| queuedOperations[previousQueuedOperationId] = QueuedOperation({ | ||||||
| operation: operation, | ||||||
| initiatorVault: msg.sender, | ||||||
| targetVault: targetVault, | ||||||
| amount: amount | ||||||
| }); | ||||||
| if (isPriceValid()) { | ||||||
| executeQueuedOperation(previousQueuedOperationId); | ||||||
| } else if (queuedPendingOperationId == 0) { | ||||||
| queuedPendingOperationId = previousQueuedOperationId; | ||||||
| requestPrice(); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| function executeQueuedOperation(uint256 operationId) public { | ||||||
| require(queuedOperations[operationId].amount > 0, 'no such operation or already executed'); | ||||||
| require(isPriceValid()); | ||||||
| // todo, we should allow these operations here to fail, but solidity try catch doesnt work inside the same contract | ||||||
| if (queuedOperations[operationId].operation == OperationType.Liquidation) { | ||||||
| try securityPool.performLiquidation(queuedOperations[operationId].initiatorVault, queuedOperations[operationId].targetVault, queuedOperations[operationId].amount) { | ||||||
| emit ExecutetedQueuedOperation(operationId, queuedOperations[operationId].operation, true); | ||||||
| } catch { | ||||||
| emit ExecutetedQueuedOperation(operationId, queuedOperations[operationId].operation, false); | ||||||
| } | ||||||
| } else if(queuedOperations[operationId].operation == OperationType.WithdrawRep) { | ||||||
| try securityPool.performWithdrawRep(queuedOperations[operationId].initiatorVault, queuedOperations[operationId].amount) { | ||||||
| emit ExecutetedQueuedOperation(operationId, queuedOperations[operationId].operation, true); | ||||||
| } catch { | ||||||
| emit ExecutetedQueuedOperation(operationId, queuedOperations[operationId].operation, false); | ||||||
| } | ||||||
| } else { | ||||||
| try securityPool.performSetSecurityBondsAllowance(queuedOperations[operationId].initiatorVault, queuedOperations[operationId].amount) { | ||||||
| emit ExecutetedQueuedOperation(operationId, queuedOperations[operationId].operation, true); | ||||||
| } catch { | ||||||
| emit ExecutetedQueuedOperation(operationId, queuedOperations[operationId].operation, false); | ||||||
| } | ||||||
| } | ||||||
| queuedOperations[operationId].amount = 0; | ||||||
| } | ||||||
| } | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using ETH instead of WETH means we can't use the same contract for other tokens. However, it is generally much simpler to build things with ETH.