Skip to content

[WIP] Credit #198

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

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
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
192 changes: 192 additions & 0 deletions contracts/rules/CreditRule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
pragma solidity ^0.5.0;

// Copyright 2018 OpenST Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import "../external/SafeMath.sol";
import "../token/TransfersAgent.sol";


/**
* Credit rules allows a budget holder to credit a user with an amount
* that the user can *only* spents in a token economy. A custom rule
* is deployed with a CreditRule acting as a TransfersAgent to execute
* transfers. CreditRule first transfers a minimum of credited amount and
* needed transfer amount, afterwards delegate the transfer execution to
* TransfersAgent itself.
*
* Steps to utilize CreditRule:
* - A custom rule is deployed by passing a CreditRule address to act as
* a transfers agent for the rule.
* - A token holder signs an executable transaction to execute the custom rule.
* - The budget holder signs an executable transaction to execute
* CreditRule::executeRule function with a credit amount, and the token
* holder's address and executeRule's function data as an argument.
*/
contract CreditRule is TransfersAgent {

/* Usings */

using SafeMath for uint256;


/** Structs */

struct CreditInfo {
uint256 amount;
bool inProgress;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we call inProgress to isAllocated/allocated ? I believe amount is the allocated amount to user.
Any specific reason to keep inProgress naming?

}


/* Storage */

address public budgetHolder;
Copy link
Contributor

Choose a reason for hiding this comment

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

We should mention that budgetHolder is a tokenHolder address. It was difficult for me to understand that until I looked into the test cases 😜


TransfersAgent public transfersAgent;

mapping(address => CreditInfo) public credits;
Copy link
Contributor

@abhayks1 abhayks1 Apr 23, 2019

Choose a reason for hiding this comment

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

Do we need to handle use case where credits for a user are only valid for a particular duration or blockheight? 💭
I might not be aware of scope of the project ⏲



/* Modifiers */

modifier onlyBudgetHolder()
{
require(
msg.sender == budgetHolder,
"Only budget holder is allowed to call."
);

_;
}


/* Special Functions */

constructor(
address _budgetHolder,
address _transfersAgent
)
public
{
require(
_budgetHolder != address(0),
"Budget holder's address is null."
);

require(
_transfersAgent != address(0),
"Transfers agent's address is null."
);

budgetHolder = _budgetHolder;

transfersAgent = TransfersAgent(_transfersAgent);
}


/* External Functions */

function executeRule(
uint256 _creditAmount,
address _to, // token holder address
bytes calldata _data // token holder execute rule data
)
external
payable
onlyBudgetHolder
returns(
bool executionStatus_,
bytes memory returnData_
)
{
require(
_creditAmount != 0,
"Credit amount is 0."
);

require(
_to != address(0),
"To (token holder) address is null."
);

require(
credits[_to].inProgress == false,
"Re-entrancy occured in crediting process."
);

credits[_to].amount = _creditAmount;
credits[_to].inProgress = true;

// solium-disable-next-line security/no-call-value
(executionStatus_, returnData_) = _to.call.value(msg.value)(_data);
}

function executeTransfers(
address _from,
address[] calldata _transfersTo,
uint256[] calldata _transfersAmount
)
external
{
if (credits[_from].inProgress) {
uint256 creditAmount = credits[_from].amount;
delete credits[_from];

uint256 sumAmount = 0;

for(uint256 i = 0; i < _transfersAmount.length; ++i) {
sumAmount = sumAmount.add(_transfersAmount[i]);
}

uint256 amountToTransferFromBudgetHolder = (
sumAmount > creditAmount ? creditAmount : sumAmount
);

executeTransfer(
budgetHolder,
_from,
amountToTransferFromBudgetHolder
);
}

transfersAgent.executeTransfers(
_from,
_transfersTo,
_transfersAmount
);
}


/* Private Functions */

function executeTransfer(
address _from,
address _beneficiary,
uint256 _amount
)
private
{
address[] memory transfersTo = new address[](1);
transfersTo[0] = _beneficiary;

uint256[] memory transfersAmount = new uint256[](1);
transfersAmount[0] = _amount;

transfersAgent.executeTransfers(
_from,
transfersTo,
transfersAmount
);
}
}
76 changes: 66 additions & 10 deletions contracts/test_doubles/unit_tests/TokenRulesSpy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,39 @@ pragma solidity ^0.5.0;
// See the License for the specific language governing permissions and
// limitations under the License.

import "../../token/EIP20TokenInterface.sol";

contract TokenRulesSpy {

/* Structs */

struct TransactionEntry {
address from;
address[] transfersTo;
uint256[] transfersAmount;
}


/* Storage */

EIP20TokenInterface public token;

TransactionEntry[] public transactions;

uint256 public transactionsLength;

mapping (address => bool) public allowedTransfers;

address public recordedFrom;

address[] public recordedTransfersTo;
uint256 public recordedTransfersToLength;
/* Special Functions */

constructor(EIP20TokenInterface _token)
public
{
require(address(_token) != address(0), "Token address is null.");

uint256[] public recordedTransfersAmount;
uint256 public recordedTransfersAmountLength;
token = _token;
}


/* External Functions */
Expand All @@ -43,20 +63,56 @@ contract TokenRulesSpy {
allowedTransfers[msg.sender] = false;
}

function fromTransaction(uint256 index)
external
view
returns (address)
{
return transactions[index].from;
}

function transfersToTransaction(uint256 index)
external
view
returns (address[] memory)
{
return transactions[index].transfersTo;
}

function transfersAmountTransaction(uint256 index)
external
view
returns (uint256[] memory)
{
return transactions[index].transfersAmount;
}

function executeTransfers(
address _from,
address[] calldata _transfersTo,
uint256[] calldata _transfersAmount
)
external
{
recordedFrom = _from;
TransactionEntry memory entry = TransactionEntry({
from: _from,
transfersTo: new address[](0),
transfersAmount: new uint256[](0)
});

transactions.push(entry);

for (uint256 i = 0; i < _transfersTo.length; ++i) {
transactions[transactionsLength].transfersTo.push(_transfersTo[i]);
}

recordedTransfersTo = _transfersTo;
recordedTransfersToLength = _transfersTo.length;
for (uint256 i = 0; i < _transfersAmount.length; ++i) {
transactions[transactionsLength].transfersAmount.push(
_transfersAmount[i]
);
}

recordedTransfersAmount = _transfersAmount;
recordedTransfersAmountLength = _transfersAmount.length;
++transactionsLength;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
pragma solidity ^0.5.0;

// Copyright 2018 OpenST Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import "../../../external/SafeMath.sol";
import "../../../rules/CreditRule.sol";

contract CustomRuleWithCredit {

/* Usings */

using SafeMath for uint256;

event Pay(
address _to,
uint256 _amount
);

/* Storage */

CreditRule public creditRule;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we introduce CreditRuleInterface contract for rules contract to integrate ❓


bool public markedToFail;


/* Special Functions */

constructor(
address _creditRule
)
public
{
require(
address(_creditRule) != address(0),
"Credit rule's address is null."
);

creditRule = CreditRule(_creditRule);
}


/* External Functions */

function makeMeFail()
external
{
markedToFail = true;
}

function pay(
address _to,
uint256 _amount
)
external
{
require(
!markedToFail,
"The function is marked to fail."
);

address[] memory transfersTo = new address[](1);
transfersTo[0] = _to;

uint256[] memory transfersAmount = new uint256[](1);
transfersAmount[0] = _amount;

creditRule.executeTransfers(
msg.sender,
transfersTo,
transfersAmount
);

emit Pay(_to, _amount);
}
}
Loading