Skip to content
This repository was archived by the owner on Jan 10, 2023. It is now read-only.
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
13 changes: 6 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,11 @@
For details on how the margin / trading system works, please check the [Whitepaper](https://www.cap.finance/whitepaper.pdf), specifically sections 4 and 4.4. Liquidation Rebates and Interest Rate no longer apply.

The items below are listed in priority order. All milestones are **ASAP**, with a target production launch date of **early January** on Arbitrum. The driving factor is high quality, speed, and code simplicity.

- [ ] Treasury fees should be paid out to a treasury address directly (set by gov)

- [ ] If submitOrder margin exceeds freeMargin, set it to the max freeMargin available
- [ ] Add MAX_FEE and other constants in Store to curtail gov powers in methods marked with onlyGov. The goal is to prevent gov from having too much power over system function, like setting a fee share too high and siphoning all the funds.
- [ ] Add automated tests, including fuzzy, to achieve > 90% coverage
- [ ] Verify Chainlink contract works as expected for Arbitrum and its sequencer. Support all other Chainlink networks (or have a custom Chainlink contract for each chain)
- [ ] Add methods "depositThroughUniswap" and "addLiquidityThroughUniswap" to allow deposits from a contract like Uniswap Router, to allow people to deposit any asset which is then automatically converted into the Store-supported currency. Potentially support other DEXes like 1inch.
- [ ] Refactor code while maintaining readability
- [ ] Run auditing tools, get more eyes on the contracts
- [ ] Deploy and test locally with the [UI](https://github.com/capofficial/ui) to make sure everything is working as expected
- [ ] Create production deploy scripts
Expand All @@ -25,9 +22,11 @@ The items below are listed in priority order. All milestones are **ASAP**, with
- [x] Flat fee
- [x] Allow submitting TP/SL with an order
- [x] Contracts: Trade, Pool, Store, Chainlink
- [x] Add MAX_FEE and other constants in Store to curtail gov powers in methods marked with onlyGov. The goal is to prevent gov from having too much power over system function, like setting a fee share too high and siphoning all the funds.
- [x] Treasury fees should be paid out to a treasury address directly (set by gov)
- [x] Refactor code while maintaining readability


## Compiling
## Compilings

```
forge build --via-ir
Expand All @@ -38,4 +37,4 @@ forge build --via-ir
```
anvil
forge script DeployLocalScript --rpc-url http://127.0.0.1:8545 --broadcast --via-ir -vvvv
```
```
58 changes: 31 additions & 27 deletions script/DeployLocal.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import "../src/mocks/MockChainlink.sol";
import "../src/mocks/MockToken.sol";

contract DeployLocalScript is Script {

uint256 public constant CURRENCY_UNIT = 10**6;

Trade public trade;
Expand All @@ -25,10 +24,10 @@ contract DeployLocalScript is Script {
function setUp() public {}

function run() public {

// this is the default mnemonic anvil uses
string memory mnemonic = "test test test test test test test test test test test junk";
(address deployer,) = deriveRememberKey(mnemonic, 0);
string
memory mnemonic = "test test test test test test test test test test test junk";
(address deployer, ) = deriveRememberKey(mnemonic, 0);

console.log("Deploying contracts with address", deployer);
vm.startBroadcast(deployer);
Expand All @@ -39,7 +38,7 @@ contract DeployLocalScript is Script {
chainlink = new MockChainlink();
console.log("Chainlink deployed to", address(chainlink));

store = new Store();
store = new Store(payable(deployer));
console.log("Store deployed to", address(store));

trade = new Trade();
Expand All @@ -58,26 +57,32 @@ contract DeployLocalScript is Script {
console.log("Contracts linked");

// Setup markets
store.setMarket("ETH-USD", Store.Market({
symbol: "ETH-USD",
feed: address(0),
maxLeverage: 50,
maxOI: 5000000 * CURRENCY_UNIT,
fee: 100,
fundingFactor: 5000,
minSize: 20 * CURRENCY_UNIT,
minSettlementTime: 1 minutes
}));
store.setMarket("BTC-USD", Store.Market({
symbol: "BTC-USD",
feed: address(0),
maxLeverage: 50,
maxOI: 5000000 * CURRENCY_UNIT,
fee: 100,
fundingFactor: 5000,
minSize: 20 * CURRENCY_UNIT,
minSettlementTime: 1 minutes
}));
store.setMarket(
"ETH-USD",
Store.Market({
symbol: "ETH-USD",
feed: address(0),
maxLeverage: 50,
maxOI: 5000000 * CURRENCY_UNIT,
fee: 100,
fundingFactor: 5000,
minSize: 20 * CURRENCY_UNIT,
minSettlementTime: 1 minutes
})
);
store.setMarket(
"BTC-USD",
Store.Market({
symbol: "BTC-USD",
feed: address(0),
maxLeverage: 50,
maxOI: 5000000 * CURRENCY_UNIT,
fee: 100,
fundingFactor: 5000,
minSize: 20 * CURRENCY_UNIT,
minSettlementTime: 1 minutes
})
);

console.log("Markets set up.");

Expand All @@ -90,7 +95,7 @@ contract DeployLocalScript is Script {

vm.stopBroadcast();

(address user,) = deriveRememberKey(mnemonic, 2);
(address user, ) = deriveRememberKey(mnemonic, 2);
console.log("Minting tokens with account", user);
vm.startBroadcast(user);

Expand All @@ -101,6 +106,5 @@ contract DeployLocalScript is Script {
console.log("Minted mock tokens for secondary account.");

vm.stopBroadcast();

}
}
13 changes: 8 additions & 5 deletions src/CLP.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,24 @@ pragma solidity ^0.8.13;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract CLP is ERC20 {

address public store;

constructor(address _store) ERC20("CLP", "CLP") {
store = _store;
}

function mint(address to, uint256 amount) public {
require(msg.sender == store, "!authorized");
_storeOnly();
_mint(to, amount);
}

function burn(address from, uint256 amount) public {
require(msg.sender == store, "!authorized");
function burn(address from, uint256 amount) public {
_storeOnly();
_burn(from, amount);
}

}
function _storeOnly() private view {
address store_ = store;
require(msg.sender == store_, "!authorized");
}
}
82 changes: 42 additions & 40 deletions src/Chainlink.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@ pragma solidity ^0.8.7;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

contract Chainlink {

// -- Constants -- //

uint256 public constant UNIT = 10**18;
uint256 public constant GRACE_PERIOD_TIME = 3600;


// -- Variables -- //

AggregatorV3Interface internal sequencerUptimeFeed;

// -- Errors -- //
Expand All @@ -21,56 +19,60 @@ contract Chainlink {
error GracePeriodNotOver();

/**
* For a list of available sequencer proxy addresses, see:
* https://docs.chain.link/docs/l2-sequencer-flag/#available-networks
*/
* For a list of available sequencer proxy addresses, see:
* https://docs.chain.link/docs/l2-sequencer-flag/#available-networks
*/

// -- Constructor -- //

constructor() {
sequencerUptimeFeed = AggregatorV3Interface(0xFdB631F5EE196F0ed6FAa767959853A9F217697D);
sequencerUptimeFeed = AggregatorV3Interface(
0xFdB631F5EE196F0ed6FAa767959853A9F217697D
);
}

function getPrice(address feed) public view returns (uint256) {

if (feed == address(0)) return 0;

(
/*uint80 roundId*/,
int256 answer,
uint256 startedAt,
/*uint256 updatedAt*/,
/*uint80 answeredInRound*/
) = sequencerUptimeFeed.latestRoundData();

// Answer == 0: Sequencer is up
// Answer == 1: Sequencer is down
bool isSequencerUp = answer == 0;
if (!isSequencerUp) {
revert SequencerDown();
}

// Make sure the grace period has passed after the sequencer is back up.
uint256 timeSinceUp = block.timestamp - startedAt;

if (timeSinceUp <= GRACE_PERIOD_TIME) {
revert GracePeriodNotOver();
}

AggregatorV3Interface priceFeed = AggregatorV3Interface(feed);
(
/*uint80 roundID*/,
int price,
/*uint startedAt*/,
/*uint timeStamp*/,
,
/*uint80 roundId*/
int256 answer,
uint256 startedAt, /*uint256 updatedAt*/
,

) = /*uint80 answeredInRound*/
sequencerUptimeFeed.latestRoundData();

// Answer == 0: Sequencer is up
// Answer == 1: Sequencer is down
bool isSequencerUp = answer == 0;
if (!isSequencerUp) {
revert SequencerDown();
}

// Make sure the grace period has passed after the sequencer is back up.
uint256 timeSinceUp = block.timestamp - startedAt;

if (timeSinceUp <= GRACE_PERIOD_TIME) {
revert GracePeriodNotOver();
}

AggregatorV3Interface priceFeed = AggregatorV3Interface(feed);
(
,
/*uint80 roundID*/
int256 price, /*uint startedAt*/
,
,

) = /*uint timeStamp*/
/*uint80 answeredInRound*/
) = priceFeed.latestRoundData();
priceFeed.latestRoundData();

uint8 decimals = priceFeed.decimals();

// Return 18 decimals standard
return uint256(price) * UNIT / 10**decimals;

return (uint256(price) * UNIT) / 10**decimals;
}

}
}
Loading