Skip to content
95 changes: 75 additions & 20 deletions src/WorkingCapital.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,68 +10,123 @@ import {IHats} from "./../hatsprotocol/src/Interfaces/IHats.sol";

contract WorkingCapital is PluginCloneable {


bytes32 public constant UPDATE_SPENDING_LIMIT_PERMISSION_ID = keccak256('UPDATE_SPENDING_LIMIT_PERMISSION');

struct MyAction {
address to;
uint256 value;
address ERC20;
}
Comment thread
alikhabazian marked this conversation as resolved.
Outdated


IHats public hatsProtocolInstance;
uint256 public hatId;
uint256 public spendingLimitETH;
uint256 public hatId;
mapping(address => uint256) public spendingLimit;

uint private currentMonth;
uint private currentYear;
uint256 private remainingBudget;
mapping(address => uint256) private remainingBudget;
address[] availableTokens;

/// @notice Initializes the contract.
/// @param _dao The associated DAO.
/// @param _hatId The id of the hat.
function initialize(IDAO _dao, uint256 _hatId, uint256 _spendingLimitETH) external initializer {
function initialize(IDAO _dao, uint256 _hatId,address[] memory _token ,uint256[] memory _spendingLimit) external initializer {
__PluginCloneable_init(_dao);
hatId = _hatId;
// TODO get this from environment per network (this is goerli)
hatsProtocolInstance = IHats(0x3bc1A0Ad72417f2d411118085256fC53CBdDd137);
spendingLimitETH = _spendingLimitETH;
require(_token.length==_spendingLimit.length,"Length of token address and spendingLimit array is not equal");
// spendingLimit = _spendingLimit;
for (uint j=0; j < _token.length; j+=1) {
spendingLimit[_token[j]]=_spendingLimit[j];
}
availableTokens=_token;
}
Comment on lines 23 to +67
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it possible to use an array of objects instead of 2 arrays?

Suggested change
/// @notice Initializes the contract.
/// @param _dao The associated DAO.
/// @param _hatId The id of the hat.
function initialize(IDAO _dao, uint256 _hatId, uint256 _spendingLimitETH) external initializer {
function initialize(IDAO _dao, uint256 _hatId,address[] memory _token ,uint256[] memory _spendingLimit) external initializer {
__PluginCloneable_init(_dao);
hatId = _hatId;
// TODO get this from environment per network (this is goerli)
hatsProtocolInstance = IHats(0x3bc1A0Ad72417f2d411118085256fC53CBdDd137);
spendingLimitETH = _spendingLimitETH;
require(_token.length==_spendingLimit.length,"Length of token address and spendingLimit array is not equal");
// spendingLimit = _spendingLimit;
for (uint j=0; j < _token.length; j+=1) {
spendingLimit[_token[j]]=_spendingLimit[j];
}
availableTokens=_token;
}
struct Budget {
address token;
uint256 spendingLimit;
}
/// @notice Initializes the contract.
/// @param _dao The associated DAO.
/// @param _hatId The id of the hat.
/// @param _budget An array of Budgest struct which is an object consisted of token address and spending amount.
function initialize(IDAO _dao, uint256 _hatId, Budget[] calldata _budget) external initializer {
__PluginCloneable_init(_dao);
hatId = _hatId;
// TODO get this from environment per network (this is goerli)
hatsProtocolInstance = IHats(0x3bc1A0Ad72417f2d411118085256fC53CBdDd137);
for (uint j=0; j < _budget.length; j+=1) {
spendingLimit[_budget[j].token] = _budget[j].spendingLimit;
availableTokens = _budget[j].token
}
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I did it manually



/// @notice Check that the given token has allowance
/// @param _token check that given token has allowance
function isTokenAvailable(address _token) internal view returns (bool) {
for (uint256 i = 0; i < availableTokens.length; i++) {
if (availableTokens[i] == _token) {
return true;
}
}
return false;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think instead of using for you can use hashtable.
Instead of using availableTokens array just use spendingLimit[token].
if spendingLimit[token] was 0 or undefined it means that the token is not available.



/// @notice Checking that can user withdraw this amount
/// @param _actions actions that would be checked
function hasRemainingBudget(IDAO.Action[] calldata _actions) internal {
/// @return generatedDAOActions IDAO.Action generated for use in execute
function hasRemainingBudget(MyAction[] calldata _actions) internal returns(IDAO.Action[] memory generatedDAOActions){
uint _currentMonth = BokkyPooBahsDateTimeLibrary.getMonth(block.timestamp);
uint _currentYear = BokkyPooBahsDateTimeLibrary.getYear(block.timestamp);
uint j=0;
for (; j < _actions.length; j+=1) { //for loop example
generatedDAOActions = new IDAO.Action[](_actions.length);
for (uint j=0; j < _actions.length; j+=1) {
address _to;
uint256 _value;
bytes memory _data;
address _token;
if(_actions[j].ERC20 == address(0)){
_to=_actions[j].to;
_value=_actions[j].value;
_data= new bytes(0);
_token=address(0);
require(isTokenAvailable(address(0)),"It is not available token in this plugin");

}
else{
_to=_actions[j].ERC20;
_value=0;
bytes memory data = abi.encodeWithSignature("transfer(address,uint256)", _actions[j].to, _actions[j].value);
_data= data;
_token=_actions[j].ERC20;
require(isTokenAvailable(_actions[j].ERC20),"It is not available token in this plugin");

}
// if we are on the month that we were
if(_currentMonth==currentMonth && _currentYear==currentYear){
require(
remainingBudget>=_actions[j].value,
remainingBudget[_token]>=_actions[j].value,
string.concat("In ",Strings.toString(j)," action you want to spend more than your limit monthly")
);
remainingBudget-=_actions[j].value;
remainingBudget[_token] -=_actions[j].value;
}
// if we are on another month
else{
currentYear = _currentYear;
currentMonth = _currentMonth;
remainingBudget=spendingLimitETH;
for (uint j=0; j < availableTokens.length; j+=1) {
remainingBudget[availableTokens[j]]=spendingLimit[availableTokens[j]];
}
require(
remainingBudget>=_actions[j].value,
remainingBudget[_token]>=_actions[j].value,
string.concat("In ",Strings.toString(j)," action you want to spend more than your limit monthly")
);
remainingBudget-=_actions[j].value;
remainingBudget[_token]-=_actions[j].value;
}

generatedDAOActions[j]=IDAO.Action(_to, _value, _data);

}
}
Comment on lines 61 to 158
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
/// @notice Checking that can user withdraw this amount
/// @param _actions actions that would be checked
function hasRemainingBudget(IDAO.Action[] calldata _actions) internal {
/// @return generatedDAOActions IDAO.Action generated for use in execute
function hasRemainingBudget(MyAction[] calldata _actions) internal returns(IDAO.Action[] memory generatedDAOActions){
uint _currentMonth = BokkyPooBahsDateTimeLibrary.getMonth(block.timestamp);
uint _currentYear = BokkyPooBahsDateTimeLibrary.getYear(block.timestamp);
uint j=0;
for (; j < _actions.length; j+=1) { //for loop example
generatedDAOActions = new IDAO.Action[](_actions.length);
for (uint j=0; j < _actions.length; j+=1) {
address _to;
uint256 _value;
bytes memory _data;
address _token;
if(_actions[j].ERC20 == address(0)){
_to=_actions[j].to;
_value=_actions[j].value;
_data= new bytes(0);
_token=address(0);
require(isTokenAvailable(address(0)),"It is not available token in this plugin");
}
else{
_to=_actions[j].ERC20;
_value=0;
bytes memory data = abi.encodeWithSignature("transfer(address,uint256)", _actions[j].to, _actions[j].value);
_data= data;
_token=_actions[j].ERC20;
require(isTokenAvailable(_actions[j].ERC20),"It is not available token in this plugin");
}
// if we are on the month that we were
if(_currentMonth==currentMonth && _currentYear==currentYear){
require(
remainingBudget>=_actions[j].value,
remainingBudget[_token]>=_actions[j].value,
string.concat("In ",Strings.toString(j)," action you want to spend more than your limit monthly")
);
remainingBudget-=_actions[j].value;
remainingBudget[_token] -=_actions[j].value;
}
// if we are on another month
else{
currentYear = _currentYear;
currentMonth = _currentMonth;
remainingBudget=spendingLimitETH;
for (uint j=0; j < availableTokens.length; j+=1) {
remainingBudget[availableTokens[j]]=spendingLimit[availableTokens[j]];
}
require(
remainingBudget>=_actions[j].value,
remainingBudget[_token]>=_actions[j].value,
string.concat("In ",Strings.toString(j)," action you want to spend more than your limit monthly")
);
remainingBudget-=_actions[j].value;
remainingBudget[_token]-=_actions[j].value;
}
generatedDAOActions[j]=IDAO.Action(_to, _value, _data);
}
}
/// @notice Checking that can user withdraw this amount
/// @param _actions actions that would be checked
/// @return generatedDAOActions IDAO.Action generated for use in execute
function hasRemainingBudget(WorkingCapitalAction[] calldata _actions) internal returns(IDAO.Action[] memory generatedDAOActions){
uint _currentMonth = BokkyPooBahsDateTimeLibrary.getMonth(block.timestamp);
uint _currentYear = BokkyPooBahsDateTimeLibrary.getYear(block.timestamp);
generatedDAOActions = new IDAO.Action[](_actions.length);
for (uint j=0; j < _actions.length; j+=1) {
address _to;
uint256 _value;
bytes memory _data;
address _token;
if(_actions[j].erc20Address == address(0)){
_to=_actions[j].to;
_value=_actions[j].value;
_data= new bytes(0);
_token=address(0);
require(isTokenAvailable(address(0)),"It is not available token in this plugin");
}
else{
_to=_actions[j].erc20Address;
_value=0;
bytes memory data = abi.encodeWithSignature("transfer(address,uint256)", _actions[j].to, _actions[j].value);
_data= data;
_token=_actions[j].erc20Address;
require(isTokenAvailable(_actions[j].erc20Address),"It is not available token in this plugin");
}
// if we are on the month that we were
if(_currentMonth==currentMonth && _currentYear==currentYear){
require(
remainingBudget[_token]>=_actions[j].value,
string.concat("In ",Strings.toString(j)," action you want to spend more than your limit monthly")
);
remainingBudget[_token] -=_actions[j].value;
}
// if we are on another month
else{
currentYear = _currentYear;
currentMonth = _currentMonth;
for (uint j=0; j < availableTokens.length; j+=1) {
remainingBudget[availableTokens[j]]=spendingLimit[availableTokens[j]];
}
require(
remainingBudget[_token]>=_actions[j].value,
string.concat("In ",Strings.toString(j)," action you want to spend more than your limit monthly")
);
remainingBudget[_token]-=_actions[j].value;
}
generatedDAOActions[j]=IDAO.Action(_to, _value, _data);
}
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I did it manually



/// @notice Executes actions in the associated DAO.
/// @param _actions The actions to be executed by the DAO.
/// @param _myActions The actions to be executed by the DAO.
function execute(
IDAO.Action[] calldata _actions
MyAction[] calldata _myActions
) external {
require(hatsProtocolInstance.isWearerOfHat(msg.sender, hatId), "Sender is not wearer of the hat");
hasRemainingBudget(_actions);
dao().execute({_callId: 0x0, _actions: _actions, _allowFailureMap: 0});
IDAO.Action [] memory idaoAction = hasRemainingBudget(_myActions);
dao().execute({_callId: 0x0, _actions: idaoAction, _allowFailureMap: 0});
}
Comment on lines 118 to 180
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
/// @notice Executes actions in the associated DAO.
/// @param _actions The actions to be executed by the DAO.
/// @param _myActions The actions to be executed by the DAO.
function execute(
IDAO.Action[] calldata _actions
MyAction[] calldata _myActions
) external {
require(hatsProtocolInstance.isWearerOfHat(msg.sender, hatId), "Sender is not wearer of the hat");
hasRemainingBudget(_actions);
dao().execute({_callId: 0x0, _actions: _actions, _allowFailureMap: 0});
IDAO.Action [] memory idaoAction = hasRemainingBudget(_myActions);
dao().execute({_callId: 0x0, _actions: idaoAction, _allowFailureMap: 0});
}
/// @notice Executes actions in the associated DAO.
/// @param _workingCapitalActions The actions to be executed by the DAO.
function execute(
WorkingCapitalAction[] calldata _workingCapitalActions
) external {
require(hatsProtocolInstance.isWearerOfHat(msg.sender, hatId), "Sender is not wearer of the hat");
IDAO.Action [] memory iDAOAction = hasRemainingBudget(_workingCapitalActions);
dao().execute({_callId: 0x0, _actions: iDAOAction, _allowFailureMap: 0});
}


/// @param _spendingLimitETH The ETH spending limit
function updateSpendingLimit(uint256 _spendingLimitETH) external auth(UPDATE_SPENDING_LIMIT_PERMISSION_ID){
spendingLimitETH = _spendingLimitETH;
/// @param _spendingLimit spending limit
function updateSpendingLimit(address _token,uint256 _spendingLimit) external auth(UPDATE_SPENDING_LIMIT_PERMISSION_ID){
spendingLimit[_token] = _spendingLimit;
}
}
5 changes: 3 additions & 2 deletions src/WorkingCapitalSetup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ contract WorkingCapitalSetup is PluginSetup {

struct InputData {
uint256 hatId;
uint256 spendingLimitETH;
uint256[] spendingLimit;
address [] token;
}

/// @notice The address of `WorkingCapital` plugin logic contract to be cloned.
Expand All @@ -39,7 +40,7 @@ contract WorkingCapitalSetup is PluginSetup {
plugin = workingCapitalImplementation.clone();

// Initialize cloned plugin contract.
WorkingCapital(plugin).initialize(IDAO(_dao), inputData.hatId, inputData.spendingLimitETH);
WorkingCapital(plugin).initialize(IDAO(_dao), inputData.hatId, inputData.token,inputData.spendingLimit);

// Prepare permissions
PermissionLib.MultiTargetPermission[]
Expand Down