Skip to content
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

PoC discount on fees when tokens are staked #13

Merged
merged 9 commits into from
May 30, 2018
Merged
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: 5 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@
[![Build Status](https://travis-ci.org/codex-protocol/contract.codex-registry.svg?branch=master)](https://travis-ci.org/codex-protocol/contract.codex-registry)

## To do
### Testing
- Extensive testing on CodexTitle

### Features
- Documentation hashes array for CodexTitle (i.e., in addition to images)
- Extensive testing on ERC900
- Token Ejection (similar to BiddableEscrow)
- 100% code coverage
- Gas optimization
- TCR for providers
- Add a version field to the tokens themselves so we can track if they've been upgraded or not. This is useful for cases where storage state in the new implementation needs to be migrated over.
- Consider adding the burn functionality back in, but maybe restricting it to onlyOwner for now.

## Notes
- Everything under the zeppelin-solidity directory was copied over from node_modules/zeppelin-solidity to give visibility into the contract code and so they can be pinned to a specific compiler version. No extensive changes have been made
- This contract is not intended to be used directly on it's own. Users should instead communicate to the contract through instances of the Biddable Widget hosted by our consortium members
- If you accidentally send ERC-20 tokens to the address where this is deployed, please email contact@codexprotocol.com to discuss retrieval

## Thanks to
Expand Down
11 changes: 8 additions & 3 deletions contracts/CodexTitleAccess.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ contract CodexTitleAccess is CodexTitleCore {
string _providerMetadataId) // TODO: convert to bytes32
public
whenNotPaused
canPayFees(creationFee)
{
return super.mint(
_to,
Expand All @@ -41,6 +42,7 @@ contract CodexTitleAccess is CodexTitleCore {
uint256 _tokenId)
public
whenNotPaused
canPayFees(transferFee)
{
return super.transferFrom(_from, _to, _tokenId);
}
Expand All @@ -54,6 +56,7 @@ contract CodexTitleAccess is CodexTitleCore {
uint256 _tokenId)
public
whenNotPaused
canPayFees(transferFee)
{
return super.safeTransferFrom(_from, _to, _tokenId);
}
Expand All @@ -68,6 +71,7 @@ contract CodexTitleAccess is CodexTitleCore {
bytes _data)
public
whenNotPaused
canPayFees(transferFee)
{
return super.safeTransferFrom(
_from,
Expand All @@ -86,9 +90,10 @@ contract CodexTitleAccess is CodexTitleCore {
bytes32 _newDescriptionHash,
bytes32[] _newFileHashes,
string _providerId, // TODO: convert to bytes32?
string _providerMetadataId // TODO: convert to bytes32?
)
public whenNotPaused
string _providerMetadataId) // TODO: convert to bytes32?
public
whenNotPaused
canPayFees(modificationFee)
{
return super.modifyMetadataHashes(
_tokenId,
Expand Down
6 changes: 0 additions & 6 deletions contracts/CodexTitleCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,6 @@ contract CodexTitleCore is CodexTitleMetadata, CodexTitleFees {
string _providerMetadataId) // TODO: convert to bytes32
public
{
if (feeRecipient != address(0)) {
require(
codexToken.transferFrom(msg.sender, feeRecipient, creationFee),
"Fee in CODX required");
}

// For now, all new tokens will be the last entry in the array
uint256 newTokenId = allTokens.length;

Expand Down
65 changes: 50 additions & 15 deletions contracts/CodexTitleFees.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
pragma solidity ^0.4.24;

import "./ERC20/ERC20.sol";
import "./ERC900/ERC900.sol";

import "./library/Pausable.sol";


Expand All @@ -11,36 +13,69 @@ import "./library/Pausable.sol";
*/
contract CodexTitleFees is Pausable {

// Address of the ERC20 Codex Protocol Token, used for fees in the contract
address public codexTokenAddress;

// Implementation of ERC20 Codex Protocol Token, used for fees in the contract
// Implementation of the ERC20 Codex Protocol Token, used for fees in the contract
ERC20 public codexToken;

// Implementation of the ERC900 Codex Protocol Stake Container,
// used to calculate discounts on fees
ERC900 public codexStakeContainer;

// Address where all contract fees are sent, i.e., the Community Fund
address public feeRecipient;

// Fee to create new tokens. 10^18 = 1 token
uint256 public creationFee = 0;

// Fee to transfer tokens. 10^18 = 1 token
uint256 public transferFee = 0;

// Fee to modify tokens. 10^18 = 1 token
uint256 public modificationFee = 0;

modifier canPayFees(uint256 baseFee) {
if (feeRecipient != address(0)) {
// TODO: Update the discount to be based on weight as opposed to just
// a binary on/off value
uint256 calculatedFee = baseFee;
if (codexStakeContainer != address(0) &&
codexStakeContainer.totalStakedFor(msg.sender) >= 0) {

calculatedFee = 0;
}

require(
codexToken.transferFrom(msg.sender, feeRecipient, calculatedFee),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs for transferFrom say Usage of this method is discouraged, use safeTransferFrom whenever possible which seems to safely handle transfers to contracts. Should we use that instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already know at this point the user who sent the tokens can hold tokens, so no harm in using transfer directly here and saving a bit of gas.

"Fee in CODX required");
}

_;
}

/**
* @dev Sets the address of the ERC20 token used for fees in the contract.
* @param _codexTokenAddress The address of the ERC20 Codex Protocol Token
* Fees are in the smallest denomination, e.g., 10^18 is 1 token.
* @param _codexToken The address of the ERC20 Codex Protocol Token
* @param _feeRecipient The address where the fees are sent
* @param _creationFee The new creation fee. 10^18 is 1 token.
* @param _creationFee The new creation fee.
* @param _transferFee The new transfer fee.
* @param _modificationFee The new modification fee.
*/
function setFees(address _codexTokenAddress, address _feeRecipient, uint256 _creationFee) external onlyOwner {
codexTokenAddress = _codexTokenAddress;
codexToken = ERC20(codexTokenAddress);
function setFees(
ERC20 _codexToken,
address _feeRecipient,
uint256 _creationFee,
uint256 _transferFee,
uint256 _modificationFee)
external onlyOwner
{
codexToken = _codexToken;
feeRecipient = _feeRecipient;
creationFee = _creationFee;
transferFee = _transferFee;
modificationFee = _modificationFee;
}

/**
* @dev Sets the creation fee in CODX
* @param _creationFee The new creation fee. 10^18 is 1 token.
*/
function setCreationFee(uint256 _creationFee) external onlyOwner {
creationFee = _creationFee;
function setStakeContainer(ERC900 _codexStakeContainer) external onlyOwner {
codexStakeContainer = _codexStakeContainer;
}
}
24 changes: 24 additions & 0 deletions contracts/ERC900/ERC900.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
pragma solidity ^0.4.24;


/**
* @title ERC900 interface
* @dev see https://github.com/ethereum/EIPs/issues/900
*/
contract ERC900 {
event Staked(address indexed user, uint256 amount, uint256 total, bytes data);
event Unstaked(address indexed user, uint256 amount, uint256 total, bytes data);

function stake(uint256 amount, bytes data) public;
function stakeFor(address user, uint256 amount, bytes data) public;
function unstake(uint256 amount, bytes data) public;
function totalStakedFor(address addr) public view returns (uint256);
function totalStaked() public view returns (uint256);
function token() public view returns (address);
function supportsHistory() public pure returns (bool);

// NOTE: Not implementing the optional functions
// function lastStakedFor(address addr) public view returns (uint256);
// function totalStakedForAt(address addr, uint256 blockNumber) public view returns (uint256);
// function totalStakedAt(uint256 blockNumber) public view returns (uint256);
}
132 changes: 132 additions & 0 deletions contracts/ERC900/ERC900BasicStakeContainer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
pragma solidity ^0.4.24;

import "./ERC900.sol";
import "../ERC20/ERC20.sol";

import "../library/SafeMath.sol";


/**
* @title ERC900BasicStakeContainer
*/
contract ERC900BasicStakeContainer is ERC900 {
using SafeMath for uint256;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some gas optimization is possible here if we deploy the library separately and then point to it (as opposed to bundling it with both contracts)


ERC20 stakingToken;

mapping (address => StakeContainer) addresses;

struct Stake {
uint256 blockNumber;
uint256 amount;
bool exists;
}

// To save on gas, rather than create a separate mapping for amountStakedFor & personalStake,
// both data structures are stored in a single mapping for a given addresses.
//
// amountStakedFor consists of all tokens staked for a given address.
// personalStake is the stake made by a given address.
//
// It's possible to have a non-existing personalStake, but have tokens in amountStakedFor
// if other users are staking on behalf of a given address.
struct StakeContainer {
// TODO: This data structure should change to represent "weight" instead of amount
uint256 amountStakedFor;

Stake personalStake;
}

modifier noExistingStake(address _address) {
require(
!addresses[_address].personalStake.exists,
"Stake already exists");
_;
}

modifier canStake(address _address, uint256 _amount) {
require(
stakingToken.transferFrom(_address, this, _amount),
"Stake required");
_;
}

constructor(ERC20 _stakingToken) public {
stakingToken = _stakingToken;
}

function stake(uint256 _amount, bytes _data)
public
noExistingStake(msg.sender)
canStake(msg.sender, _amount)
{
addresses[msg.sender].personalStake = Stake(block.number, _amount, true);
addresses[msg.sender].amountStakedFor.add(_amount);

emit Staked(
msg.sender,
_amount,
totalStakedFor(msg.sender),
_data);
}

function stakeFor(address _user, uint256 _amount, bytes _data)
public
noExistingStake(msg.sender)
canStake(msg.sender, _amount)
{
addresses[msg.sender].personalStake = Stake(block.number, _amount, true);

// Notice here that we are increasing the staked amount for _user
// instead of msg.sender
addresses[_user].amountStakedFor.add(_amount);

emit Staked(
_user,
_amount,
totalStakedFor(_user),
_data);
}

function unstake(uint256 _amount, bytes _data) public {
require(addresses[msg.sender].personalStake.exists, "Stake doesn't exist");

// Transfer the staked tokens from this contract back tot he sender
// Notice that we are using transfer instead of transferFrom here, so
// no approval is needed before hand.
require(
stakingToken.transfer(msg.sender, _amount),
"Unable to withdraw stake");

// If this was a complete withdrawal, then delete the previous stake to reset
// the block number and exists flag
if (addresses[msg.sender].personalStake.amount == 0) {
delete addresses[msg.sender].personalStake;
addresses[msg.sender].amountStakedFor = 0;
} else {
addresses[msg.sender].amountStakedFor.sub(_amount);
}

emit Unstaked(
msg.sender,
_amount,
totalStakedFor(msg.sender),
_data);
}

function totalStakedFor(address _address) public view returns (uint256) {
return addresses[_address].amountStakedFor;
}

function totalStaked() public view returns (uint256) {
return stakingToken.balanceOf(this);
}

function token() public view returns (address) {
return stakingToken;
}

function supportsHistory() public pure returns (bool) {
return false;
}
}
6 changes: 6 additions & 0 deletions migrations/3_deploy_stakecontainer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const CodexToken = artifacts.require('./CodexToken.sol')
const ERC900BasicStakeContainer = artifacts.require('./ERC900BasicStakeContainer.sol')

module.exports = (deployer) => {
deployer.deploy(ERC900BasicStakeContainer, CodexToken.address)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const CodexTitle = artifacts.require('./CodexTitle.sol')

module.exports = (deployer, network, accounts) => {
module.exports = (deployer) => {
deployer.deploy(CodexTitle)
}
2 changes: 2 additions & 0 deletions migrations/4_deploy_proxy.js → migrations/5_deploy_proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ module.exports = async (deployer, network, accounts) => {
})
.catch((error) => {
console.log(error)

throw error
})
}
Loading