-
-
Save mcchan1/7f781c90afe468dc7810bf6747d3906d to your computer and use it in GitHub Desktop.
pragma solidity 0.5.3; | |
/* | |
adminFee = userTokenBalances[GUILD][token] / adminFeeDenominator; | |
if adminFeeDenominator is greater than or equal to 200, then adminFee will be less than or equal to .5% | |
-- allows LAO to reduce fees if desired, but not required to reduce . | |
Using OpenZeppelin's Ownable.sol contract to manage who is the service provider. It will be The LAO at launch. | |
withdrawAdminFee can direct an amount up to .5% of the assets under management (adminFee) | |
to any address (laoFund) after 90 days from the last time the function was called. | |
This function can only be called by the Owner. | |
HOW: | |
Summoner as agent and sole member will enter into agreement with LAO as service provider. | |
LAO will be the "Owner" under the Ownable.sol contract | |
Trust assumption: (upheld by valid legal documents under Delaware law): if Member's go through voting process to name | |
a service provider other than LAO, than LAO will step down and transfer Owner to the new named service provider. | |
LAO - will probably use OpenLaw relayer to automate call every 90 days to ensure adminFee is collected in a timely manner. | |
*/ | |
//ABILITY TO TRANSFER SERVICE CONTRACT USING OpenZeppelin's OWNABLE.SOL | |
import "https://github.com/OpenZeppelin/openzeppelin-solidity/contracts/ownership/Ownable.sol"; | |
contract Moloch is ReentrancyGuard, Ownable { | |
using SafeMath for uint256; | |
//CONSTRUCTOR ADD ON PARAMS | |
// + laoFundAddress = _laoFundAddress; . | |
//+ lastPaymentTime = now; | |
//....REST IS SAME AS MOLOCH DAO CODE | |
//EVENTS - Remove Event | |
//event WithdrawAdminFee(address indexed laoFundAddress, address token [], uint256 amount ); | |
uint256 constant paymentPeriod = 90 days; | |
uint256 public lastPaymentTime; //this will set as 'now' in construtor = summoningTime; | |
address public laoFundAddress; //This field MUST be set in constructor or set to default to summoner here. | |
uint256 public adminFeeDenominator = 200; //initial denominator | |
// @dev Owner can change amount of adminFee and direction of funds | |
// @param adminFeeDenoimnator must be >= 200. Greater than 200, will equal 0.5% or less of assets. | |
//@param laoFundAddress - where the Owner wants the funds to go. | |
function setAdminFee (uint256 _adminFeeDenominator, address _laoFundAddress) public nonReentrant onlyOwner{ | |
require(_adminFeeDenominator >= 200); | |
adminFeeDenominator = _adminFeeDenominator; | |
laoFundAddress = _laoFundAddress; | |
} //end of setAdminFee | |
function withdrawAdminFee () public nonReentrant { | |
require (now >= lastPaymentTime.add(paymentPeriod), "90 days have not passed since last withdrawal"); | |
lastPaymentTime = now; | |
//local variables to save gas by reading from storage only 1x | |
uint256 denominator = adminFeeDenominator; | |
address recipient = laoFundAddress; | |
for (uint256 i = 0; i < approvedTokens.length; i++) { | |
address token = approvedTokens[i]; | |
uint256 amount = userTokenBalances[GUILD][token] / denominator; | |
if (amount > 0) { // otherwise skip for efficiency | |
userTokenBalances[GUILD][token] -= amount; | |
userTokenBalances[recipient][token] += amount; | |
} | |
} | |
// Remove Event emit WithdrawAdminFee(laoFundAddress,token, amount); | |
} //end of withdrawAdminFee | |
}//K end |
thanks! @HeikoFisch. Made corrections 1 & 2.
@adridadou regarding point 3. I'm ok with either... setAdminFee
would be add a line: require(_adminFeeDenominator >= 200);
and removing it from line 52 in withdrawAdminFee
I didn't really want to add more to the constructor, so just set adminFeeDenominator
and laoFundAddress
after deployment. For point 4, I can live with this, but lmk what you think.
@HeikoFisch - quick fyi, spoke with @adridadou. Made recommended change for point 3. Point 4 - we can live with.
Ok, @mcchan1, thanks for the feedback.
You forgot the underscore, though. (Or move the test after the next line.)
Also, note that if you don't initialize adminFeeDenominator
and you forget to call setAdminFee
before the first call to withdrawAdminFee
, the latter will not only revert, but also consume all available gas. I recommend to initialize adminFeeDenominator
to 200 and laoFundAddress
to a suitable value.
@HeikioFisch - Thanks again...put that underscore back in. For initializing adminFeeDenominator
and laoFundAddress
, do you think just adding into the Moloch's constructor is best...so that way it forces the summoner to declare at deployment? Also, one unrelated question -- if the adminFeeDenominator
is at an odd number, for example 377, do you see any issues?
For initializing
adminFeeDenominator
andlaoFundAddress
, do you think just adding into the Moloch's constructor is best...so that way it forces the summoner to declare at deployment?
Adding laoFundAddress
to the constructor is, in my opinion, a good idea. Of course, you could just use the summoner as the initial laoFundAddress
if that suits your needs (which I don't know). Forcing someone to choose an initial laoFundAddress
-- what you proposed -- seems a bit safer, but ultimately that's a choice you have to make, not so much a technical question.
As to the adminFeeDenominator
, I'd just set that to 200 when the variable is declared, because 200 is the default value anyway. That is, I'd replace the current line 39 with uint256 public adminFeeDenominator = 200;
.
If you choose to set it in the constructor, that's fine too, but you have verify that it is at least 200, like in the setAdminFee
function.
Also, one unrelated question -- if the
adminFeeDenominator
is at an odd number, for example 377, do you see any issues?
No, it doesn't make a difference whether the number is odd or even. In general, this is an integer division, that is, the fractional part of the result is cut off. So, for example,
9999 / 200 = 49,
10000 / 200 = 50,
10001 / 200 = 50, etc.
Nothing special happens if the denominator is an odd number:
123456 / 377 = 327.
@mcchan1
At the risk of belaboring the obvious, I'd like to point out that this function needs to be tested -- in particular, because it is one of the gas-critical functions that iterate over all tokens. You have to make sure that it stays within the block gas limit when the token counts hit their limits.
Note also that if the event (which should currently result in a compile-time error) is supposed to be emitted in the loop body, this will increase gas costs significantly, so I'd recommend figuring out first what event(s) you want...
Maybe this is all obvious to you, but because you wrote in the chat that the admin fee function just needs to be added, I became a little bit concerned about this and thought that it was better to be explicit about it...
@HeikoFisch -Thanks for making this explicit since I wasn’t clear in the chat about this function being production ready w/o testing @awrigh01. @adridadou - Definitely will need your help writing the tests before actual deployment. Also, I don’t think we need the Event, since this will not be used very often and I don’t see a need to surface in the UI.
@mcchan1, let me add 2 things:
-
A single event outside of the loop is no problem at all, in case you'd like to have that.
-
One of the last-minute changes in the Moloch makes it advisable to replace Line 61 (
unsafeInternalTransfer(GUILD, laoFundAddress, token, amount);
with the following two lines:
userTokenBalances[GUILD][token] -= amount;
userTokenBalances[laoFundAddress][token] += amount;
Essentially, we're doing the transfer manually, like in _ragequit
, because unsafeInternalTransfer
has become more expensive recently.
With these changes and the current token limits, I'd be very surprised if this function was able to exceed the block gas limit. Nevertheless, testing is always a good idea... ;)
thank you for the update @HeikoFisch - very much appreciated! The changes have been made, I'll keep the event out for the moment, unless our UI devs demand one. Also, I like that the the token limit was added in the new version.
👍
One (I hope) final remark on this, @mcchan1.
Contrary to my expectation, it looks like the compiler is not able to optimize the SLOAD
s for the adminFeeDenominator
and the laoFundAddress
out of the loop. This means that withdrawAdminFee
could be up to ~480k gas more expensive than a ragequit
. With the Moloch token limits, you'd be at around 82% of the current block gas limit (ragequit ~77%). If you'd like to play it safe(r) or just want to avoid wasting gas, you could add
uint256 denominator = adminFeeDenominator;
address recipient = laoFundAddress;
at current line 56, and then replace
adminFeeDenominator
in current line 59 withdenominator
andlaoFundAddress
in current line 62 withrecipient
.
So, rather than reading from storage in every iteration of the loop, you'd only read from storage once -- into local variables -- and then use these variables in the loop body.
Again, these are mostly theoretical considerations; testing the code is recommended...
It might be obvious, but let me add that the state variables lastPaymentTime
and laoFundAddress
must be set in the constructor, not where the variables are declared, because the summoningTime
and the initial value for the laoFundAddress
(no matter whether explicitly given or the summoner) will only be available in the constructor.
@HeikoFisch thanks again! always interested in safer and more gas efficiency. On your last note - I made it explicit with lastPaymentTime = now
in the constructor, rather than inherit if from the constructor's summoningTime = now
and same for laoFundAddress
Looks mostly ok to me...
Remarks:
;
at the end of line 39.setAdminFee
should also benonReentrant
.>= 200
when a new denominator is set, not every time the set value is used. So I'd remove line 52, and insertrequire(_adminFeeDenominator >= 200);
at the beginning of functionsetAdminFee
. In this case,adminFeeDenominator
andlaoFundAddress
should be initialized and/or you mustn't forget to callsetAdminFee
before the first payment period is over.If this seems too complicated, you can just leave it as it is...
laoFundAddress
, or the old one will still receive the fees. If and when a change of thelaoFundAddress
is desired, it is also important to do it in a timely manner, because everyone can callwithdrawAdminFee
as soon as the payment period is over.