-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 3800252
Showing
28 changed files
with
84,007 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
||
# dependencies | ||
/node_modules | ||
/.pnp | ||
.pnp.js | ||
|
||
# testing | ||
/coverage | ||
|
||
# production | ||
/build | ||
|
||
# misc | ||
.DS_Store | ||
.env.local | ||
.env.development.local | ||
.env.test.local | ||
.env.production.local | ||
|
||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
|
||
node_modules | ||
.env | ||
coverage | ||
coverage.json | ||
typechain | ||
|
||
#Hardhat files | ||
cache | ||
artifacts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# DeFi Money Streamnig using Sablier | ||
|
||
Sablier allows to stream money using smart contracts. Any ERC20 token can be used to stream. In this particular project, only one ERC20 token is supported and can be minted from the app itself. | ||
|
||
Install the node modules using | ||
|
||
``` | ||
npm install | ||
``` | ||
|
||
Run a local blockchanin using | ||
|
||
``` | ||
npx hardhat node | ||
``` | ||
|
||
To deploy the contracts run | ||
|
||
``` | ||
npx hardhat run --network localhost scripts/deploy.js | ||
``` | ||
|
||
To start the react server run | ||
|
||
``` | ||
npm start | ||
``` | ||
|
||
<figure class="video_container"> | ||
<iframe src="https://www.youtube.com/watch?v=sYl4xG_7a4I" frameborder="0" allowfullscreen="true"> </iframe> | ||
</figure> | ||
|
||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity >=0.5.17; | ||
|
||
/** | ||
* @title Careful Math | ||
* @author Compound | ||
* @notice Derived from OpenZeppelin's SafeMath library | ||
* https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol | ||
*/ | ||
contract CarefulMath { | ||
|
||
/** | ||
* @dev Possible error codes that we can return | ||
*/ | ||
enum MathError { | ||
NO_ERROR, | ||
DIVISION_BY_ZERO, | ||
INTEGER_OVERFLOW, | ||
INTEGER_UNDERFLOW | ||
} | ||
|
||
/** | ||
* @dev Multiplies two numbers, returns an error on overflow. | ||
*/ | ||
function mulUInt(uint a, uint b) internal pure returns (MathError, uint) { | ||
if (a == 0) { | ||
return (MathError.NO_ERROR, 0); | ||
} | ||
|
||
uint c = a * b; | ||
|
||
if (c / a != b) { | ||
return (MathError.INTEGER_OVERFLOW, 0); | ||
} else { | ||
return (MathError.NO_ERROR, c); | ||
} | ||
} | ||
|
||
/** | ||
* @dev Integer division of two numbers, truncating the quotient. | ||
*/ | ||
function divUInt(uint a, uint b) internal pure returns (MathError, uint) { | ||
if (b == 0) { | ||
return (MathError.DIVISION_BY_ZERO, 0); | ||
} | ||
|
||
return (MathError.NO_ERROR, a / b); | ||
} | ||
|
||
/** | ||
* @dev Subtracts two numbers, returns an error on overflow (i.e. if subtrahend is greater than minuend). | ||
*/ | ||
function subUInt(uint a, uint b) internal pure returns (MathError, uint) { | ||
if (b <= a) { | ||
return (MathError.NO_ERROR, a - b); | ||
} else { | ||
return (MathError.INTEGER_UNDERFLOW, 0); | ||
} | ||
} | ||
|
||
/** | ||
* @dev Adds two numbers, returns an error on overflow. | ||
*/ | ||
function addUInt(uint a, uint b) internal pure returns (MathError, uint) { | ||
uint c = a + b; | ||
|
||
if (c >= a) { | ||
return (MathError.NO_ERROR, c); | ||
} else { | ||
return (MathError.INTEGER_OVERFLOW, 0); | ||
} | ||
} | ||
|
||
/** | ||
* @dev add a and b and then subtract c | ||
*/ | ||
function addThenSubUInt(uint a, uint b, uint c) internal pure returns (MathError, uint) { | ||
(MathError err0, uint sum) = addUInt(a, b); | ||
|
||
if (err0 != MathError.NO_ERROR) { | ||
return (err0, 0); | ||
} | ||
|
||
return subUInt(sum, c); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity >=0.5.17; | ||
|
||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; | ||
import "./CarefulMath.sol"; | ||
|
||
import "./interfaces/ISablier.sol"; | ||
import "./Types.sol"; | ||
|
||
import "hardhat/console.sol"; | ||
|
||
contract Sablier is ISablier, ReentrancyGuard, CarefulMath { | ||
using SafeERC20 for IERC20; | ||
|
||
uint256 public nextStreamId; | ||
|
||
mapping(uint256 => Types.Stream) private streams; | ||
mapping(address => uint256[]) public streamMap; | ||
mapping(address => uint256[]) public streamRecipient; | ||
|
||
modifier onlySenderOrRecipient(uint256 streamId) { | ||
require( | ||
msg.sender == streams[streamId].sender || msg.sender == streams[streamId].recipient, | ||
"caller is not the sender or the recipient of the stream" | ||
); | ||
_; | ||
} | ||
|
||
modifier streamExists(uint256 streamId) { | ||
require(streams[streamId].isEntity, "stream does not exist"); | ||
_; | ||
} | ||
|
||
constructor() { | ||
nextStreamId = 100000; | ||
} | ||
|
||
function getStream(uint256 streamId) | ||
external | ||
view | ||
override | ||
streamExists(streamId) | ||
returns ( | ||
address sender, | ||
address recipient, | ||
uint256 deposit, | ||
address tokenAddress, | ||
uint256 startTime, | ||
uint256 stopTime, | ||
uint256 remainingBalance, | ||
uint256 ratePerSecond | ||
) | ||
{ | ||
sender = streams[streamId].sender; | ||
recipient = streams[streamId].recipient; | ||
deposit = streams[streamId].deposit; | ||
tokenAddress = streams[streamId].tokenAddress; | ||
startTime = streams[streamId].startTime; | ||
stopTime = streams[streamId].stopTime; | ||
remainingBalance = streams[streamId].remainingBalance; | ||
ratePerSecond = streams[streamId].ratePerSecond; | ||
} | ||
|
||
function deltaOf(uint256 streamId) public view streamExists(streamId) returns (uint256 delta) { | ||
Types.Stream memory stream = streams[streamId]; | ||
if (block.timestamp <= stream.startTime) return 0; | ||
if (block.timestamp < stream.stopTime) return block.timestamp - stream.startTime; | ||
return stream.stopTime - stream.startTime; | ||
} | ||
|
||
struct BalanceOfLocalVars { | ||
MathError mathErr; | ||
uint256 recipientBalance; | ||
uint256 withdrawalAmount; | ||
uint256 senderBalance; | ||
} | ||
|
||
function balanceOf(uint256 streamId, address who) public override view streamExists(streamId) returns (uint256 balance) { | ||
Types.Stream memory stream = streams[streamId]; | ||
BalanceOfLocalVars memory vars; | ||
|
||
uint256 delta = deltaOf(streamId); | ||
(vars.mathErr, vars.recipientBalance) = mulUInt(delta, stream.ratePerSecond); | ||
require(vars.mathErr == MathError.NO_ERROR, "recipient balance calculation error"); | ||
|
||
|
||
if (stream.deposit > stream.remainingBalance) { | ||
(vars.mathErr, vars.withdrawalAmount) = subUInt(stream.deposit, stream.remainingBalance); | ||
assert(vars.mathErr == MathError.NO_ERROR); | ||
(vars.mathErr, vars.recipientBalance) = subUInt(vars.recipientBalance, vars.withdrawalAmount); | ||
/* `withdrawalAmount` cannot and should not be bigger than `recipientBalance`. */ | ||
assert(vars.mathErr == MathError.NO_ERROR); | ||
} | ||
|
||
if (who == stream.recipient) return vars.recipientBalance; | ||
if (who == stream.sender) { | ||
(vars.mathErr, vars.senderBalance) = subUInt(stream.remainingBalance, vars.recipientBalance); | ||
/* `recipientBalance` cannot and should not be bigger than `remainingBalance`. */ | ||
assert(vars.mathErr == MathError.NO_ERROR); | ||
return vars.senderBalance; | ||
} | ||
return 0; | ||
} | ||
|
||
/*** Public Effects & Interactions Functions ***/ | ||
|
||
struct CreateStreamLocalVars { | ||
MathError mathErr; | ||
uint256 duration; | ||
uint256 ratePerSecond; | ||
} | ||
|
||
function createStream(address recipient, uint256 deposit, address tokenAddress, uint256 startTime, uint256 stopTime) | ||
public | ||
override | ||
returns (uint256) | ||
{ | ||
require(recipient != address(0x00), "stream to the zero address"); | ||
require(recipient != address(this), "stream to the contract itself"); | ||
require(recipient != msg.sender, "stream to the caller"); | ||
require(deposit > 0, "deposit is zero"); | ||
require(startTime >= block.timestamp, "start time before block.timestamp"); | ||
require(stopTime > startTime, "stop time before the start time"); | ||
|
||
CreateStreamLocalVars memory vars; | ||
(vars.mathErr, vars.duration) = subUInt(stopTime, startTime); | ||
/* `subUInt` can only return MathError.INTEGER_UNDERFLOW but we know `stopTime` is higher than `startTime`. */ | ||
assert(vars.mathErr == MathError.NO_ERROR); | ||
|
||
/* Without this, the rate per second would be zero. */ | ||
require(deposit >= vars.duration, "deposit smaller than time delta"); | ||
|
||
/* This condition avoids dealing with remainders */ | ||
require(deposit % vars.duration == 0, "deposit not multiple of time delta"); | ||
|
||
(vars.mathErr, vars.ratePerSecond) = divUInt(deposit, vars.duration); | ||
/* `divUInt` can only return MathError.DIVISION_BY_ZERO but we know `duration` is not zero. */ | ||
assert(vars.mathErr == MathError.NO_ERROR); | ||
|
||
/* Create and store the stream object. */ | ||
uint256 streamId = nextStreamId; | ||
streams[streamId] = Types.Stream({ | ||
remainingBalance: deposit, | ||
deposit: deposit, | ||
isEntity: true, | ||
ratePerSecond: vars.ratePerSecond, | ||
recipient: recipient, | ||
sender: msg.sender, | ||
startTime: startTime, | ||
stopTime: stopTime, | ||
tokenAddress: tokenAddress | ||
}); | ||
|
||
streamMap[msg.sender].push(streamId); | ||
streamRecipient[recipient].push(streamId); | ||
/* Increment the next stream id. */ | ||
(vars.mathErr, nextStreamId) = addUInt(nextStreamId, uint256(1)); | ||
require(vars.mathErr == MathError.NO_ERROR, "next stream id calculation error"); | ||
|
||
IERC20(tokenAddress).safeTransferFrom(msg.sender, address(this), deposit); | ||
emit CreateStream(streamId, msg.sender, recipient, deposit, tokenAddress, startTime, stopTime); | ||
return streamId; | ||
} | ||
|
||
function withdrawFromStream(uint256 streamId, uint256 amount) | ||
external | ||
nonReentrant | ||
override | ||
streamExists(streamId) | ||
onlySenderOrRecipient(streamId) | ||
returns (bool) | ||
{ | ||
require(amount > 0, "amount is zero"); | ||
Types.Stream memory stream = streams[streamId]; | ||
|
||
uint256 balance = balanceOf(streamId, stream.recipient); | ||
require(balance >= amount, "amount exceeds the available balance"); | ||
|
||
MathError mathErr; | ||
(mathErr, streams[streamId].remainingBalance) = subUInt(stream.remainingBalance, amount); | ||
/** | ||
* `subUInt` can only return MathError.INTEGER_UNDERFLOW but we know that `remainingBalance` is at least | ||
* as big as `amount`. | ||
*/ | ||
assert(mathErr == MathError.NO_ERROR); | ||
|
||
if (streams[streamId].remainingBalance == 0) delete streams[streamId]; | ||
|
||
IERC20(stream.tokenAddress).safeTransfer(stream.recipient, amount); | ||
emit WithdrawFromStream(streamId, stream.recipient, amount); | ||
return true; | ||
} | ||
|
||
|
||
function cancelStream(uint256 streamId) | ||
external | ||
nonReentrant | ||
override | ||
streamExists(streamId) | ||
onlySenderOrRecipient(streamId) | ||
returns (bool) | ||
{ | ||
Types.Stream memory stream = streams[streamId]; | ||
uint256 senderBalance = balanceOf(streamId, stream.sender); | ||
uint256 recipientBalance = balanceOf(streamId, stream.recipient); | ||
|
||
delete streams[streamId]; | ||
|
||
IERC20 token = IERC20(stream.tokenAddress); | ||
if (recipientBalance > 0) token.safeTransfer(stream.recipient, recipientBalance); | ||
if (senderBalance > 0) token.safeTransfer(stream.sender, senderBalance); | ||
|
||
emit CancelStream(streamId, stream.sender, stream.recipient, senderBalance, recipientBalance); | ||
return true; | ||
} | ||
|
||
function getUserStreamList(address _currentUser) | ||
external | ||
view | ||
returns(uint256[] memory) | ||
{ | ||
return streamMap[_currentUser]; | ||
} | ||
|
||
function getStreamRecipientList(address _currentUser) | ||
external | ||
view | ||
returns(uint256[] memory) | ||
{ | ||
return streamRecipient[_currentUser]; | ||
} | ||
} |
Oops, something went wrong.