Skip to content

Instantly share code, notes, and snippets.

@OxMarco
Created September 26, 2023 17:21
Show Gist options
  • Save OxMarco/910918a52c45dd01f9ad7ded2ef07c27 to your computer and use it in GitHub Desktop.
Save OxMarco/910918a52c45dd01f9ad7ded2ef07c27 to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import { Counters } from "@openzeppelin/contracts/utils/Counters.sol";
import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
contract CDS is ERC721 {
using SafeERC20 for IERC20;
using Counters for Counters.Counter;
struct SwapData {
IERC20 token;
address source;
uint256 amount;
uint256 premium;
uint256 deadline;
uint256 maturity;
bool active;
}
enum Status { CANCELLED, TRIGGERED, UNTRIGGERED }
event Created(uint256 indexed tokenID, SwapData data);
event Subscribed(uint256 indexed tokenID, address indexed subscriber);
event Closed(uint256 indexed tokenID, address indexed owner, SwapData data, Status indexed status);
error Revert(string reason);
Counters.Counter public id;
mapping(uint256 tokenID => SwapData data) public data;
mapping(uint256 tokenID => string url) public metadata;
constructor() ERC721("Credit Default Swap", "CDS") {}
modifier exists(uint256 tokenID) {
if(!_exists(tokenID)) revert Revert({ reason: "Token does not exist" });
_;
}
function create(address _token, uint256 _amount, uint256 _premium, uint256 _deadline, uint256 _maturity, string memory _url) external {
if(_maturity < block.timestamp) revert Revert({ reason: "Invalid maturity date" });
if(_maturity > _deadline) revert Revert({ reason: "Invalid deadline date" });
IERC20 token = IERC20(_token);
token.safeTransferFrom(msg.sender, address(this), _premium);
id.increment();
data[id.current()] = SwapData({
token: token,
source: msg.sender,
amount: _amount,
premium: _premium,
deadline: _deadline,
maturity: _maturity,
active: false
});
metadata[id.current()] = _url;
_safeMint(msg.sender, id.current());
emit Created(id.current(), data[id.current()]);
}
function subscribe(uint256 tokenID) external exists(tokenID) {
SwapData storage d = data[tokenID];
if(d.active) revert Revert({ reason: "Subscribed already" });
if(d.deadline < block.timestamp) revert Revert({ reason: "Expired" });
if(d.maturity > block.timestamp) revert Revert({ reason: "Matured already" });
d.active = true;
d.token.safeTransferFrom(msg.sender, address(this), d.amount);
emit Subscribed(tokenID, msg.sender);
}
function close(uint256 tokenID) external exists(tokenID) {
SwapData memory d = data[tokenID];
if(d.maturity < block.timestamp) revert Revert({ reason: "Not matured yet" });
Status status = Status.CANCELLED;
address owner = ownerOf(tokenID);
if(!d.active) {
d.token.safeTransfer(d.source, d.amount);
} else {
status = _handlePayoff(d, owner);
}
_burn(tokenID);
delete data[tokenID];
delete metadata[tokenID];
emit Closed(tokenID, owner, d, status);
}
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
string memory customMetadata = metadata[tokenId];
string memory baseURI = _baseURI();
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, customMetadata)) : customMetadata;
}
function _handlePayoff(SwapData memory d, address owner) internal returns (Status) {
uint256 notional = d.amount + d.premium;
// TODO use oracle
bool outcome = true;
Status status;
if(outcome) {
d.token.safeTransfer(d.source, notional);
status = Status.UNTRIGGERED;
} else {
d.token.safeTransfer(owner, notional);
status = Status.TRIGGERED;
}
return status;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment