Skip to content

Commit

Permalink
added fund agent role instead of single token operator
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-iobuilders committed Jul 19, 2019
1 parent 21d3341 commit 93fb184
Show file tree
Hide file tree
Showing 11 changed files with 2,032 additions and 159 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,23 @@ In general, it is not advisable to place explicit routing instructions for debit

The following diagram shows the sequence of the payout creation and execution.

![Fundable Token: Fund executed](http://www.plantuml.com/plantuml/png/bP0nhi8m44HxdsALFXVunLSG0eag57004qyQmZhQNKiS7pkcGWH4PutVQ9wsK_DYwJQviq8srFvJiqyS_6K2dU8LtFnHb00fgNlzexOKbZTj8vlumQYV8TwYv6R1-z96SAlO-z5PuW3L3xEByNFamCVAkoQLcCOSwbU_7B2Ea8h_geWxXcHOLrmCg-2kii5ru93l_G00)
![Fundable Token: Fund executed](http://www.plantuml.com/plantuml/png/ZP0n3i8m34NtdCBgtWime7O0YGa6E41eVXae3h8JYUFJr7H11NNCz_FFanjDNb9-3EwYa9RgBLNxpC5V1z0vti7LXg84I4dTzupgUO7Q6pYDS7aSom8CdoVBrK-97LJ_b4zUdzu3dunt5bC_XhfaaSIpzX0ZLeZWXIud_1QPFZIDdR71DU1GRlS6)

### Fund cancelled

The following diagram shows the sequence of the payout creation and cancellation.

![Fundable Token: Fund cancelled](http://www.plantuml.com/plantuml/png/SoWkIImgAStDuGejJYroLD2rKr1oAyrBIKpAILK8oSzEpLEoKiWlIaaj0eboeSifw88qWDaAplbv9KM9oIKA_WMfHOab-KL0dI3b_Y3TgIbS-6n8JinBJiqXsmfdiFOSe2KEgNafGDy00000)
![Fundable Token: Fund cancelled](http://www.plantuml.com/plantuml/png/SoWkIImgAStDuGejJYroLD2rKr1oAyrBIKpAILK8oSzEpLEoKiWlIaaj0eboeSifwC8qA3Ycf-QL01M3EFuW3Qaf-CnCJinBJiqXnL1di8uSeB4EgNaf82S30000)

### Fund rejected

The following diagram shows the sequence of the payout creation and rejection.

![Fundable Token: Fund rejected](http://www.plantuml.com/plantuml/png/SoWkIImgAStDuGejJYroLD2rKr1oAyrBIKpAILK8oSzEpLEoKiWlIaaj0eboeSifw88qWDaAplbv9KM9oIKA_WMfHOab-KL0dI3b_Y3TgIbSN3ZLORUrA3KhDRa4ZxLW5qCj2G8h1jhYa9gN0lGE0000)
![Fundable Token: Fund rejected](http://www.plantuml.com/plantuml/png/SoWkIImgAStDuGejJYroLD2rKr1oAyrBIKpAILK8oSzEpLEoKiWlIaaj0eboeSifwC8qA3Ycf-QL01M3EFuW3QaWvGWPx4ONfMQb9fVWCHliBAYnGM35G7CTKlDIG6u60000)

## State diagram

![Fundable: State Diagram](http://www.plantuml.com/plantuml/png/TP71JiGm34Jl-Oev8_O7EA2L44YSG6XlY4EQk518xLNY2FhtjDscAK8zLMDFvzdECQcDcljWNg_US3ZSuYbwYkHly93PBy8itzdmoCzH7ALqiQpIh7-Uv8iumKR2cTXt_0uLDAEwECcZ6Q0V19sSSC8QhLF8cQ9LYMR3E8ssTkGipKuOtpPXdflyDPgi9Qf0MHG3AJgUEuqDbXr1sbdcH1l0lDBz3xciPUcYJ2sV_WBwZiSoMearSNkYkuaNGWbSfQcDoGFXCA0XE1j2FSg_k1Vw9BUELzHsXtrJBUy3zrxXIEnp7tu1)
![Fundable: State Diagram](http://www.plantuml.com/plantuml/png/VL51JiGm3Bpd5ND6x0Sue9KGI9n0g3V48KtSfP2rLuaZwEzfqsspArMSeh77C_PadzH6pSTWtcy-iDlTuoLwYkJly9JPdu4vluNmpAzH7AKqKrPerib6leaJR2ISY7tF1wYW7T7C98zsW4KtZiCUYDLSY3QVD7VaHD5gBumVcr0M9N-BDYjqv6XrOL4CfEYvT5eRB3k2T0NcHB4Qb1iUVybbNQvSaAdbvjhWsFDOHYUnAbvcyZ3vXR08hj3KniI1S1Yc89mDeQImBVT6N-JMzHPKR_YFLClRXbUnxudzzFb_)

## Install

Expand Down Expand Up @@ -76,7 +76,7 @@ The unit tests use a JSON version of this standard, which can be seem below.
"funds": [
{
"amount": 1.00,
"fundSourceId": "caaa2bd3-dc42-436a-b70b-d1d7dac23741",
"fundingSubjectId": "caaa2bd3-dc42-436a-b70b-d1d7dac23741",
"receiverInformation": "Example funds receiver information"
}
]
Expand Down
32 changes: 32 additions & 0 deletions contracts/FundAgentRole.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
pragma solidity ^0.5.0;

import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
import "openzeppelin-solidity/contracts/access/Roles.sol";


contract FundAgentRole is Ownable {
using Roles for Roles.Role;

Roles.Role internal fundAgents;

modifier onlyFundAgent() {
_onlyFundAgent();
_;
}

function addFundAgent(address _who) public onlyOwner {
fundAgents.add(_who);
}

function removeFundAgent(address _who) public onlyOwner {
fundAgents.remove(_who);
}

function isFundAgent(address _who) public view returns (bool) {
return fundAgents.has(_who);
}

function _onlyFundAgent() private view {
require(isFundAgent(msg.sender), "FundAgentRole: caller is not a fund agent");
}
}
44 changes: 20 additions & 24 deletions contracts/Fundable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ pragma solidity ^0.5.0;
import "./IFundable.sol";
import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";
import "./libraries/StringUtil.sol";
import "./FundAgentRole.sol";


contract Fundable is IFundable, ERC20 {
contract Fundable is IFundable, ERC20, FundAgentRole {
using StringUtil for string;

struct FundableData {
Expand All @@ -16,25 +17,23 @@ contract Fundable is IFundable, ERC20 {
FundStatusCode status;
}

address public tokenOperator;
// walletToFund -> authorized -> true/false
mapping(address => mapping(address => bool)) public fundOperators;

mapping(bytes32 => FundableData) private orderedFunds;

constructor() public {
tokenOperator = msg.sender;
fundAgents.add(msg.sender);
}

function authorizeFundOperator(address orderer) external returns (bool) {
function authorizeFundOperator(address orderer) public returns (bool) {
require(fundOperators[msg.sender][orderer] == false, "The operator is already authorized");

fundOperators[msg.sender][orderer] = true;
emit FundOperatorAuthorized(msg.sender, orderer);
return true;
}

function revokeFundOperator(address orderer) external returns (bool) {
function revokeFundOperator(address orderer) public returns (bool) {
require(fundOperators[msg.sender][orderer], "The operator is already not authorized");

fundOperators[msg.sender][orderer] = false;
Expand All @@ -43,10 +42,10 @@ contract Fundable is IFundable, ERC20 {
}

function orderFund(
string calldata operationId,
string memory operationId,
uint256 value,
string calldata instructions
) external returns (bool)
string memory instructions
) public returns (bool)
{
return _orderFund(
operationId,
Expand All @@ -57,11 +56,11 @@ contract Fundable is IFundable, ERC20 {
}

function orderFundFrom(
string calldata operationId,
string memory operationId,
address walletToFund,
uint256 value,
string calldata instructions
) external returns (bool)
string memory instructions
) public returns (bool)
{
require(address(0) != walletToFund, "WalletToFund address must not be zero address");
require(_isFundOperatorFor(msg.sender, walletToFund), "This operator is not authorized");
Expand All @@ -73,7 +72,7 @@ contract Fundable is IFundable, ERC20 {
);
}

function cancelFund(string calldata operationId) external returns (bool) {
function cancelFund(string memory operationId) public returns (bool) {
FundableData storage fund = orderedFunds[operationId.toHash()];
require(fund.status == FundStatusCode.Ordered, "A fund can only be cancelled in status Ordered");
require(fund.walletToFund == msg.sender || fund.orderer == msg.sender, "Only the wallet who receives the fund can cancel");
Expand All @@ -82,17 +81,15 @@ contract Fundable is IFundable, ERC20 {
return true;
}

function processFund(string calldata operationId) external returns (bool) {
require(tokenOperator == msg.sender, "A fund can only be processed by the fund operator");
function processFund(string memory operationId) public onlyFundAgent returns (bool) {
FundableData storage fund = orderedFunds[operationId.toHash()];
require(fund.status == FundStatusCode.Ordered, "A fund can only be put in process from status Ordered");
fund.status = FundStatusCode.InProcess;
emit FundInProcess(fund.orderer, operationId);
return true;
}

function executeFund(string calldata operationId) external returns (bool) {
require(tokenOperator == msg.sender, "A fund can only be executed by the fund operator");
function executeFund(string memory operationId) public onlyFundAgent returns (bool) {
FundableData storage fund = orderedFunds[operationId.toHash()];
require(fund.status == FundStatusCode.InProcess, "A fund can only be executed from status InProcess");
fund.status = FundStatusCode.Executed;
Expand All @@ -102,29 +99,28 @@ contract Fundable is IFundable, ERC20 {
}

function rejectFund(
string calldata operationId,
string calldata reason
) external returns (bool)
string memory operationId,
string memory reason
) public onlyFundAgent returns (bool)
{
FundableData storage fund = orderedFunds[operationId.toHash()];
require(
fund.status == FundStatusCode.Ordered || fund.status == FundStatusCode.InProcess,
"A fund can only be rejected if the status is ordered or in progress"
);
require(tokenOperator == msg.sender, "A fund can only be rejected by the token operator");

fund.status = FundStatusCode.Rejected;
emit FundRejected(fund.orderer, operationId, reason);
return true;
}

function isFundOperatorFor(address walletToFund, address orderer) external view returns (bool) {
function isFundOperatorFor(address walletToFund, address orderer) public view returns (bool) {
return _isFundOperatorFor(walletToFund, orderer);
}

function retrieveFundData(
string calldata operationId
) external view returns (
string memory operationId
) public view returns (
address orderer,
address walletToFund,
uint256 value,
Expand Down
7 changes: 3 additions & 4 deletions diagrams/fundable_cancel.puml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
@startuml
UserA -> "Fundable Token": orderFund
"Fundable Token" --> "Token Contract Operator": FundOrdered

"Fundable Token" --> "Fund Agent": FundOrdered
UserA -> "Fundable Token": cancelFund
"Fundable Token" --> "Token Contract Operator": FundCancelled
@enduml
"Fundable Token" --> "Fund Agent": FundCancelled
@enduml
10 changes: 5 additions & 5 deletions diagrams/fundable_execute.puml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
@startuml
UserA -> "Fundable Token": orderFund
"Fundable Token" --> "Token Contract Operator": FundOrdered
"Token Contract Operator" -> "Fundable Token": processFund
"Fundable Token" --> "Fund Agent": FundOrdered
"Fund Agent" -> "Fundable Token": processFund
"Fundable Token" --> "UserA": FundInProcess
"userA funds" --> "Token Contract Operator": transfer funds
"Token Contract Operator" -> "Fundable Token": executeFund
"UserA funds" --> "Fund Agent": transfer funds
"Fund Agent" -> "Fundable Token": executeFund
"Fundable Token" --> UserA: FundExecuted
@enduml
@enduml
7 changes: 3 additions & 4 deletions diagrams/fundable_reject.puml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
@startuml
UserA -> "Fundable Token": orderFund
"Fundable Token" --> "Token Contract Operator": FundOrdered

"Token Contract Operator" -> "Fundable Token": rejectFund
"Fundable Token" --> "Fund Agent": FundOrdered
"Fund Agent" -> "Fundable Token": rejectFund
"Fundable Token" --> "UserA": FundRejected
@enduml
@enduml
6 changes: 3 additions & 3 deletions diagrams/state_diagram.puml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
@startuml
[*] --> Ordered: FundOrdered
Ordered --> InProcess: FundInProcess
Ordered: Tokens are pending to be processed by the token contract operator
Ordered: Tokens are pending to be processed by a fund agent
InProcess: The fund operation was started, but the tokens are not yet in the balance of the user
InProcess --> Executed: FundExecuted
Executed: The tokens are minted for the user
Ordered --> Cancelled: FundCancelled
Cancelled: Can only be called by the user
Ordered --> Rejected: FundRejected
InProcess --> Rejected: FundRejected
Rejected: Can only be called by the token contract operator
@enduml
Rejected: Can only be called by a fund agent
@enduml
Loading

0 comments on commit 93fb184

Please sign in to comment.