Skip to content

Instantly share code, notes, and snippets.

@xhliu
Last active November 3, 2023 18:32
Show Gist options
  • Save xhliu/b60a51b0d7b07c00f80c85c261407827 to your computer and use it in GitHub Desktop.
Save xhliu/b60a51b0d7b07c00f80c85c261407827 to your computer and use it in GitHub Desktop.
irs
//https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/ownership/Ownable.sol
import 'openzeppelin-solidity/contracts/ownership/Ownable.sol';
import 'openzeppelin-solidity/contracts/math/SafeMath.sol';
pragma solidity ^0.4.18;
contract IntSwap is Ownable{
using SafeMath for uint256;
event Deposited(address indexed payee, uint256 amount, uint256 timestamp);
event Withdrawn(address indexed payee, uint256 amount, uint256 timestamp);
struct IntSwapTerms {
uint total_escrow_amount; // we will only accept ETH for demo. Escrow set at 0.2% of notional amount.
uint swap_rate; //expressed in 7 decimal points converted to integer--i.e. 0.0239539 or 2.39539% x10000000 is 239539
uint termStartUnixTimestamp;
uint termEndUnixTimestamp; //month, day, year or # of months from start timestamp
// address fixed_to_var_owner;
// address var_to_fixed_owner;
}
struct ProposalOwner {
uint notional_amount; //this amount is used to calculate the escrow amount
uint owner_input_rate; //current interest rate of proposal owner
uint termEndUnixTimestamp; //month, day, year or # of months from start timestamp. Mature date of the terms
string owner_input_rate_type; //fixed or variable. If fixed, then proposal owner is fixed_to_var_owner, vice versa.
}
struct CounterpartyEscrow {
uint escrowDepositTimestamp;
uint escrow_amount_deposited;
}
struct ProposalEscrow {
uint escrowDepositTimestamp;
uint escrow_amount_deposited; //expressed in wei/ether
}
// Given that Solidity does not support floating points, we encode
// interest rates as percentages scaled up by a factor of 10,000
// As such, interest rates can, at a maximum, have 4 decimal places
// of precision.
//10,000,000 => 1% interest rate
//1,000,000 => 0.1% interest rate
//100,000 => 0.01% interest rate
// To convert an encoded interest rate into its equivalent multiplier
// (for purposes of calculating total interest), divide the notional amount by INTEREST_RATE_SCALING_FACTOR_PERCENT -- e.g.
// 10,000,000 => 0.01 interest multiplier
uint public constant INTEREST_RATE_SCALING_FACTOR_PERCENT = 10 ** 7; //10,000,000
uint public constant INTEREST_RATE_SCALING_FACTOR_MULTIPLIER = INTEREST_RATE_SCALING_FACTOR_PERCENT * 100; //1,000,000,000
uint public num;
string public fixedRate = 'fixed';
string public variableRate = 'variable';
address public proposalOwner; //proposal owner address
address public counterparty; // counterparty owner address
mapping (address => IntSwapTerms) public contractAddressToContractTerms; //this uses the address of the contract to map to the contract terms
mapping (address => ProposalOwner) public proposalAddressToProposalOwner; //this uses the address of the proposer to get his information
mapping (address => ProposalEscrow) public proposalAddressToProposalEscrow;
mapping (address => CounterpartyEscrow) public counterpartyAddressToCounterpartyAddressEscrow;
mapping (address => uint256) public payeeAddressToPayAmount;
modifier onlyProposalOwner() {
// require(msg.sender == contractAddressToContractTerms[address(this)][var_to_fixed_owner],"Only the contract terms propasal owner can call this function.");
require(msg.sender == proposalOwner, "Only the contract terms propasal owner can call this function.");
_;
}
modifier onlyCounterparty() {
// require(msg.sender == contractAddressToContractTerms[address(this)][fixed_to_var_owner],"Only the contract terms conterparty can call this function.");
require(msg.sender == counterparty, "Only the contract terms conterparty can call this function.");
_;
}
modifier hasMatured(){
IntSwapTerms memory int_swap_terms = contractAddressToContractTerms[address(this)]; //address(this) is the address of this contract
num++; //need to spend gas in order to get now timestamp
require (now > int_swap_terms.termEndUnixTimestamp, "Contract has not matured yet.");
_;
}
//run this function to register as the IntSwap contract proposal owner
//Consider to make this function be called by the proposer
function registerProposalOwner(uint _notional_amount, uint _owner_input_rate, uint matured_date, string _owner_input_rate_type, address _proposal_owner) public {
require(proposalAddressToProposalOwner[proposalOwner].notional_amount == 0);
require( _proposal_owner == msg.sender);
ProposalOwner memory proposal_owner = ProposalOwner({notional_amount: _notional_amount, owner_input_rate: _owner_input_rate, termEndUnixTimestamp: matured_date, owner_input_rate_type: _owner_input_rate_type});
// if (keccak256(_owner_input_rate_type) == keccak256(fixedRate)) {
// IntSwapTerms memory int_swap_terms = IntSwapTerms({fixed_to_var_owner: _proposal_owner});
// proposalOwner = _proposal_owner; //store the address of the proposal owner
// } else {
// IntSwapTerms memory int_swap_terms = IntSwapTerms({var_to_fixed_owner: _proposal_owner});
// proposalOwner = _proposal_owner;
// }
proposalOwner = _proposal_owner;
//map the proposal owner address to proposal owner struct
proposalAddressToProposalOwner[_proposal_owner] = proposal_owner;
//map the contract's address to the struct
// contractAddressToContractTerms[address(this)] = int_swap_terms;
}
//consider to have the counterparty call this function
function registerCounterparty(address _counterparty) public {
require (proposalOwner != address(0), "Needs to register the proposal owner first");
require(counterparty == address(0));
require(_counterparty == msg.sender);
// IntSwapTerms memory int_swap_terms = contractAddressToContractTerms[address(this)]; //address(this) is the address of this contract
//check if the address is not set. address(0) == empty address
//if empty, then this is the counterparty
// if (int_swap_terms.fixed_to_var_owner == address(0)) {
// int_swap_terms.fixed_to_var_owner = _counterparty;
// counterparty = _counterparty; //store the address of the counterparty
// } else {
// int_swap_terms.var_to_fixed_owner = _counterparty;
// counterparty = _counterparty;
// }
//map the counterparty address to counterpary struct
// counterpartyAddressToCounterparty[_counterparty];
counterparty = _counterparty;
}
//can only be called when escrow is deposited
function calculateEscrowAmount (uint _escrowPercent) internal returns (uint) {
//Escrow set at 0.2% of notional (2,000,000 is the converted _escrowPercent)
// IntSwapTerms memory int_swap_terms = contractAddressToContractTerms[address(this)];
ProposalOwner memory proposal_owner = proposalAddressToProposalOwner[proposalOwner];
//ex. notional_amount = 100,000
// escrow amount could have decimals (cents) need to look at in future
uint escrow_amount = proposal_owner.notional_amount.mul(_escrowPercent).div(INTEREST_RATE_SCALING_FACTOR_MULTIPLIER);
return escrow_amount;
}
//the escrow amount is already calculated on the frontend.
function proposerDepositIntoEscrow (uint _escrowAmount, uint _escrowPercent) public payable onlyProposalOwner {
require(msg.value == _escrowAmount); //msg.value(in wei or ether) has to be the same as the escrow amount
require(_escrowAmount == calculateEscrowAmount(_escrowPercent)); //require the proposal owner to send the same amount of calculated escrow
require(proposalAddressToProposalEscrow[proposalOwner].escrow_amount_deposited == 0);
ProposalEscrow memory proposal_escrow = ProposalEscrow({escrowDepositTimestamp: now, escrow_amount_deposited: _escrowAmount});
proposalAddressToProposalEscrow[proposalOwner] = proposal_escrow;
emit Deposited(proposalOwner, _escrowAmount, now);
}
function counterpartyDepositIntoEscrow (uint _escrowAmount, uint _escrowPercent) public payable onlyCounterparty {
require(msg.value == _escrowAmount);
require(_escrowAmount == calculateEscrowAmount(_escrowPercent)); //require the proposal owner to send the same amount of calculated escrow
require(counterpartyAddressToCounterpartyAddressEscrow[counterparty].escrow_amount_deposited == 0);
CounterpartyEscrow memory counterparty_escrow = CounterpartyEscrow({escrowDepositTimestamp: block.timestamp, escrow_amount_deposited: _escrowAmount});
counterpartyAddressToCounterpartyAddressEscrow[counterparty] = counterparty_escrow;
emit Deposited(counterparty, _escrowAmount, block.timestamp);
}
function escrowDepositsOf(address payee) public view returns (uint256) {
require(payee == proposalOwner || payee == counterparty);
if(payee == proposalOwner){
return proposalAddressToProposalEscrow[payee].escrow_amount_deposited;
}
if(payee == counterparty){
return counterpartyAddressToCounterpartyAddressEscrow[payee].escrow_amount_deposited;
}
}
// _swap_rate = 2.88% based on forward rate of what the US LIBOR 1 month market is expected in 23 months if it is a 24 month contract (1st day of contract maturity month)
function mintIntSwap (uint _swap_rate) onlyOwner public {
// IntSwapTerms memory int_swap_terms = contractAddressToContractTerms[address(this)];
ProposalOwner memory proposal_owner = proposalAddressToProposalOwner[proposalOwner]; //this gives us the proposal owner struct
ProposalEscrow memory proposal_escrow = proposalAddressToProposalEscrow[proposalOwner]; //This gives us the proposal escrow struct
CounterpartyEscrow memory counterparty_escrow = counterpartyAddressToCounterpartyAddressEscrow[counterparty];
require (proposal_escrow.escrow_amount_deposited != 0); //making sure proposal owner has deposited the money
require (counterparty_escrow.escrow_amount_deposited != 0);
require (proposal_escrow.escrow_amount_deposited == counterparty_escrow.escrow_amount_deposited);
require (_swap_rate > 0);
require (proposalOwner != 0);
require (counterparty != 0);
uint totalEscrowAmount = proposal_escrow.escrow_amount_deposited.add(counterparty_escrow.escrow_amount_deposited);
uint timeStampStart = now;
IntSwapTerms memory intswap_terms = IntSwapTerms({total_escrow_amount: totalEscrowAmount, swap_rate: _swap_rate, termStartUnixTimestamp: timeStampStart, termEndUnixTimestamp: proposal_owner.termEndUnixTimestamp});
contractAddressToContractTerms[address(this)] = intswap_terms;
}
// function getEndLibor(uint end_LIBOR) internal hasMatured onlyOwner returns (uint){
// //this function only called when contract is matured
// //contact oracle (or array for demo) to get one-month LIBOR at beginning of maturity month
// // uint end_LIBOR = msg.data;
// return end_LIBOR;
// }
//for the purpose of testing, take the hasMatured modifier out
function VarToFixedPayoutCalc(uint _end_LIBOR) public onlyOwner returns (uint VarToFixedPayout){
// 2.9% = 0.029 LIBOR will be scaled to 29,000,000
// 0.88% = 0.0088 scaled to 8,800,000 LIBOR
// _end_LIBOR to be passed into function = 29,000,000
// swap rate will be scaled to 28,800,000 (2.88% = 0.0288)
//notional amount = 100,000
//if LIBOR increases (is positive) VarToFixed owner gets a profit
//if LIBOR decreases (is negative) VarToFixed owner gets a loss
//divide rates by 120,000,000 (with 7 zeroes) to convert from annual to monthly and from integer to 7 decimal places
ProposalOwner memory proposal_owner = proposalAddressToProposalOwner[proposalOwner];
IntSwapTerms memory int_swap_terms = contractAddressToContractTerms[address(this)]; //address(this) is the address of this contract
uint VarToFixedGain;
uint VarToFixedLoss;
// uint end_LIBOR = getEndLibor();
uint end_LIBOR = _end_LIBOR;
uint _swap_rate = int_swap_terms.swap_rate;
uint _notional_amount = proposal_owner.notional_amount;
uint _escrow_amount;
uint months = 12;
uint MONTHLY_INTEREST_RATE_SCALING_FACTOR_MULTIPLIER = months.mul(INTEREST_RATE_SCALING_FACTOR_MULTIPLIER);
address varToFixedOwner;
if (keccak256(proposal_owner.owner_input_rate_type) == keccak256(variableRate)) {
_escrow_amount = proposalAddressToProposalEscrow[proposalOwner].escrow_amount_deposited;
varToFixedOwner = proposalOwner;
} else {
_escrow_amount = counterpartyAddressToCounterpartyAddressEscrow[counterparty].escrow_amount_deposited;
varToFixedOwner = counterparty;
}
// when end_LIBOR gone up they experience a gain
if (end_LIBOR > _swap_rate){
VarToFixedGain = (_notional_amount.mul(end_LIBOR.sub(_swap_rate))).div(MONTHLY_INTEREST_RATE_SCALING_FACTOR_MULTIPLIER); //166.666
}
// when end_LIBOR gone down they experience a loss
if (end_LIBOR <= _swap_rate){
VarToFixedLoss = (_notional_amount.mul(_swap_rate.sub(end_LIBOR))).div(MONTHLY_INTEREST_RATE_SCALING_FACTOR_MULTIPLIER);
}
//VarToFixedGain is limited by the _escrow_amount
// if the VarToFixedGain is greater than the _escrow_amount
// gain cannot exceed the other party's escrow
if (VarToFixedGain > _escrow_amount){
VarToFixedGain = _escrow_amount;
}
//if the VarToFixedLoss is greater than the _escrow_amount
//loss cannot excess your own escrow
if (VarToFixedLoss > _escrow_amount){
VarToFixedLoss = _escrow_amount;
}
VarToFixedPayout = _escrow_amount + VarToFixedGain - VarToFixedLoss;
payeeAddressToPayAmount[varToFixedOwner] = VarToFixedPayout;
return VarToFixedPayout;
}
function FixedToVarPayoutCalc(uint _end_LIBOR) public onlyOwner returns(uint FixedToVarPayout){
//if LIBOR increases (is positive) FixedToVar owner gets a loss
//if LIBOR decreases (is negative) FixedToVar owner gets a profit
ProposalOwner memory proposal_owner = proposalAddressToProposalOwner[proposalOwner];
IntSwapTerms memory int_swap_terms = contractAddressToContractTerms[address(this)]; //address(this) is the address of this contract
uint FixedToVarGain;
uint FixedToVarLoss;
// uint end_LIBOR = getEndLibor();
uint end_LIBOR = _end_LIBOR;
uint _swap_rate = int_swap_terms.swap_rate;
uint _notional_amount = proposal_owner.notional_amount;
uint _escrow_amount;
uint months = 12;
uint MONTHLY_INTEREST_RATE_SCALING_FACTOR_MULTIPLIER = months.mul(INTEREST_RATE_SCALING_FACTOR_MULTIPLIER);
address fixedToVarOwner;
if (keccak256(proposal_owner.owner_input_rate_type) == keccak256(fixedRate)) {
_escrow_amount = proposalAddressToProposalEscrow[proposalOwner].escrow_amount_deposited;
fixedToVarOwner = proposalOwner;
} else {
_escrow_amount = counterpartyAddressToCounterpartyAddressEscrow[counterparty].escrow_amount_deposited;
fixedToVarOwner = counterparty;
}
if (end_LIBOR < _swap_rate){
FixedToVarGain = (_notional_amount.mul(_swap_rate.sub(end_LIBOR))).div(MONTHLY_INTEREST_RATE_SCALING_FACTOR_MULTIPLIER);
}
if (end_LIBOR >= _swap_rate){
FixedToVarLoss = (_notional_amount.mul(end_LIBOR.sub(_swap_rate))).div(MONTHLY_INTEREST_RATE_SCALING_FACTOR_MULTIPLIER);
}
if (FixedToVarGain > _escrow_amount){
FixedToVarGain = _escrow_amount; //ok to replace value of FixedToVarGain variable or shd we use new name?
}
if (FixedToVarLoss > _escrow_amount){
FixedToVarLoss = _escrow_amount; //ok to replace value of variable value or shd we use new name?
}
FixedToVarPayout = _escrow_amount + FixedToVarGain - FixedToVarLoss;
payeeAddressToPayAmount[fixedToVarOwner] = FixedToVarPayout;
return FixedToVarPayout;
}
// hasMatured modififer is taken out for the purpose of demo
function proposalOwnerWithdrawPayment() public onlyProposalOwner {
address payee = msg.sender;
uint256 payment = payeeAddressToPayAmount[payee];
require(payment != 0, "There is nothing to withdraw");
require(address(this).balance >= payment);
// reduce the balance first to prevent re-entrancy attacks
payeeAddressToPayAmount[payee] = 0;
payee.transfer(payment);
emit Withdrawn(payee, payment, block.timestamp);
}
// hasMatured modififer is taken out for the purpose of demo
function counterpartyOwnerWithdrawPayment() public onlyCounterparty {
address payee = msg.sender;
uint256 payment = payeeAddressToPayAmount[payee];
require(payment != 0,"There is nothing to withdraw");
require(address(this).balance >= payment);
// reduce the balance first to prevent re-entrancy attacks
payeeAddressToPayAmount[payee] = 0;
payee.transfer(payment);
emit Withdrawn(payee, payment, block.timestamp);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment