Skip to content

Commit

Permalink
Merge pull request #75 from endaoment/audit-m02
Browse files Browse the repository at this point in the history
Audit Item M02
  • Loading branch information
CantelopePeel authored Aug 8, 2020
2 parents 45edea5 + 992de87 commit d5860cb
Show file tree
Hide file tree
Showing 11 changed files with 600 additions and 230 deletions.
24 changes: 23 additions & 1 deletion contracts/Administratable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ contract Administratable {
* roles are admin (0), accountant (2), and reviewer (3).
*/
modifier onlyAdminOrRole(address adminContractAddress, IEndaomentAdmin.Role role) {
_onlyAdminOrRole(adminContractAddress, role);
_;
}

function _onlyAdminOrRole(address adminContractAddress, IEndaomentAdmin.Role role)
private
view
{

require(
adminContractAddress != address(0),
"Administratable: Admin must not be the zero address"
Expand Down Expand Up @@ -73,10 +82,23 @@ contract Administratable {
);
}
}
}

// TODO(rheeger): write docs in audit-l05
modifier onlyAddressOrAdminOrRole(address allowedAddress, address adminContractAddress, IEndaomentAdmin.Role role) {
require(
allowedAddress != address(0),
"Administratable: Allowed address must not be the zero address"
);

bool isAllowed = (msg.sender == allowedAddress);

if (!isAllowed) {
_onlyAdminOrRole(adminContractAddress, role);
}
_;
}

/**
* @notice Returns true if two strings are equal, false otherwise
* @param s1 First string to compare
Expand Down
129 changes: 91 additions & 38 deletions contracts/Fund.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,26 @@ contract Fund is Administratable {
using SafeMath for uint256;
using SafeERC20 for IERC20;

// ========== STATE VARIABLES ==========
// ========== STRUCTS & EVENTS ==========

struct Grant {
string description;
uint256 value;
address recipient;
bool complete;
}

event ManagerChanged(address newManager);
event GrantCreated(string grantId, Grant grant);
event GrantUpdated(string grantId, Grant grant);
event GrantRejected(string grantId);
event GrantFinalized(string grantId, Grant grant);

// ========== STATE VARIABLES ==========

address public manager;
IFactory public fundFactoryContract;
Grant[] public grants;

event ManagerChanged(address newManager);
event GrantCreated(Grant grant);
event GrantFinalized(Grant grant);
mapping(string => Grant) public pendingGrants; // grant UUID to Grant

// ========== CONSTRUCTOR ==========
/**
Expand All @@ -52,15 +57,6 @@ contract Fund is Administratable {
fundFactoryContract = IFactory(fundFactory);
}

// ========== Admin Management ==========
/**
* @notice Restricts method access to fund's manager
*/
modifier restricted() {
require(msg.sender == manager, "Fund: This method is only callable by the fund manager.");
_;
}

// ========== Fund Management & Info ==========
/**
* @notice Change Fund Primary Advisor
Expand Down Expand Up @@ -100,7 +96,6 @@ contract Fund is Administratable {
external
view
returns (
uint256,
uint256,
address
)
Expand All @@ -109,51 +104,115 @@ contract Fund is Administratable {
IERC20 tokenContract = IERC20(tokenAddress);
uint256 balance = tokenContract.balanceOf(address(this));

return (balance, grants.length, manager);
return (balance, manager);
}

/**
* @notice Create new Grant Recommendation
* @param grantId UUID representing this grant
* @param description The address of the Owner.
* @param value The value of the grant in base units.
* @param recipient The address of the recieving organization's contract.
* @param orgFactoryContractAddress Address of the orgFactory Contract.
*/
function createGrant(
string memory description,
string calldata grantId,
string calldata description,
uint256 value,
address recipient,
address orgFactoryContractAddress
) public restricted {
) public onlyAddressOrAdminOrRole(manager, fundFactoryContract.endaomentAdmin(), IEndaomentAdmin.Role.REVIEWER) {
require(!isEqual(grantId, ""), "Fund: Must provide a grantId");
require(!isEqual(description, ""), "Fund: Must provide a description");
require(
checkRecipient(recipient, orgFactoryContractAddress) == true,
"Fund: Recipient contract was not created by the OrgFactory and is not allowed."
);
require(
pendingGrants[grantId].recipient == address(0),
"Fund: Grant was already created."
);

Grant memory newGrant = Grant({
description: description,
value: value,
recipient: recipient,
complete: false
});
emit GrantCreated(newGrant);
grants.push(newGrant);
emit GrantCreated(grantId, newGrant);
pendingGrants[grantId] = newGrant;
}

/**
* @notice Update Grant Recommendation
* @param grantId UUID representing this grant
* @param description The address of the Owner.
* @param value The value of the grant in base units.
* @param recipient The address of the recieving organization's contract.
*/
function updateGrant(
string calldata grantId,
string calldata description,
uint256 value,
address recipient,
address orgFactoryContractAddress
) public onlyAddressOrAdminOrRole(manager, fundFactoryContract.endaomentAdmin(), IEndaomentAdmin.Role.REVIEWER) {
require(!isEqual(grantId, ""), "Fund: Must provide a grantId");
require(!isEqual(description, ""), "Fund: Must provide a description");
require(
checkRecipient(recipient, orgFactoryContractAddress) == true,
"Fund: Recipient contract was not created by the OrgFactory and is not allowed."
);
require(
pendingGrants[grantId].recipient != address(0),
"Fund: Grant does not exist."
);
require(pendingGrants[grantId].complete == false,
"Fund: Grant is already finalized."
);
Grant memory replacementGrant = Grant({
description: description,
value: value,
recipient: recipient,
complete: false
});
pendingGrants[grantId] = replacementGrant;
emit GrantUpdated(grantId, replacementGrant);
}

/**
* @notice Reject Grant Recommendation
* @param grantId UUID representing this grant
*/
function rejectGrant(
string calldata grantId
) public onlyAddressOrAdminOrRole(manager, fundFactoryContract.endaomentAdmin(), IEndaomentAdmin.Role.REVIEWER) {
require(!isEqual(grantId, ""), "Fund: Must provide a grantId");
require(
pendingGrants[grantId].recipient != address(0),
"Fund: Grant does not exist."
);
require(pendingGrants[grantId].complete == false,
"Fund: Grant is already finalized."
);

delete pendingGrants[grantId];
emit GrantRejected(grantId);
}

/**
* @notice Approve Grant Recommendation
* @param index This Grant's index position
* @param grantId UUID of the grant being finalized
* @param tokenAddress The stablecoin's token address.
*/
function finalizeGrant(uint256 index, address tokenAddress)
public
onlyAdminOrRole(fundFactoryContract.endaomentAdmin(), IEndaomentAdmin.Role.ACCOUNTANT)
{
require(index < grants.length, "Fund: Index out of range");
function finalizeGrant(
string calldata grantId,
address tokenAddress
) public onlyAdminOrRole(fundFactoryContract.endaomentAdmin(), IEndaomentAdmin.Role.ACCOUNTANT) {
require(!isEqual(grantId, ""), "Fund: Must provide a grantId");
require(tokenAddress != address(0), "Fund: Token address cannot be the zero address");
EndaomentAdmin endaomentAdmin = EndaomentAdmin(fundFactoryContract.endaomentAdmin());
Grant storage grant = grants[index];
Grant storage grant = pendingGrants[grantId];
require(grant.recipient != address(0), "Fund: Grant does not exist");
// Checks
require(grant.complete == false, "Fund: Grant is already finalized.");
// Effects
Expand All @@ -163,16 +222,10 @@ contract Fund is Administratable {
uint256 fee = grant.value.div(100);
uint256 finalGrant = grant.value.sub(fee);
grant.complete = true;
emit GrantFinalized(grant);
emit GrantFinalized(grantId, grant);
// Interactions
tokenContract.safeTransfer(endaomentAdmin.getRoleAddress(IEndaomentAdmin.Role.ADMIN), fee);
address endaomentAdminAdminAddress = EndaomentAdmin(fundFactoryContract.endaomentAdmin()).getRoleAddress(IEndaomentAdmin.Role.ADMIN);
tokenContract.safeTransfer(endaomentAdminAdminAddress, fee);
tokenContract.safeTransfer(grant.recipient, finalGrant);
}

/**
* @notice Returns total number of grants submitted to the fund.
*/
function getGrantsCount() external view returns (uint256) {
return grants.length;
}
}
11 changes: 1 addition & 10 deletions contracts/FundFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ import "./Fund.sol";
* allowedOrgs.
*/
contract FundFactory is EndaomentAdminStorage {
// ========== STATE VARIABLES ==========
Fund[] public createdFunds;
// ========== EVENTS ==========
event FundCreated(address indexed newAddress);

// ========== CONSTRUCTOR ==========
Expand All @@ -41,14 +40,6 @@ contract FundFactory is EndaomentAdminStorage {
{
require(managerAddress != address(0), "FundFactory: Manager cannot be the zero address");
Fund newFund = new Fund(managerAddress, address(this));
createdFunds.push(newFund);
emit FundCreated(address(newFund));
}

/**
* @notice Returns total number of funds created by the factory.
*/
function countFunds() external view returns (uint256) {
return createdFunds.length;
}
}
Loading

0 comments on commit d5860cb

Please sign in to comment.