Closed dob closed 6 years ago
Issue Status: 1. Open 2. Started 3. Submitted 4. Done
This issue now has a funding of 0.5 ETH (233.56 USD @ $467.11/ETH) attached to it.
Issue Status: 1. Open 2. Started 3. Submitted 4. Done
Work has been started.
These users each claimed they can complete the work by 11 months, 3 weeks from now. Please review their questions below:
Q) So, the callerTokenAmount
worth of newly generated tokens will then first be transferred to directly called MultiMinerMine
contract, which will then forward it to the original msg.sender
?
Q) So, the callerTokenAmount worth of newly generated tokens will then first be transferred to directly called MultiMinerMine contract, which will then forward it to the original msg.sender ?
I believe so. If the user calls the MerkleMine generate contract directly, they are msg.sender
in the context, and the callerTokenAmount gets transfered to them. But in this case, since the MultiMerkleMine
contract is going to invoke the MerkleMine.generate()
function, I believe msg.sender will appear to MerkleMine to be the MultiMerkleMine
contract itself. Which then needs to forward the token to the original caller. There may be other patterns to consider such as delegatecall, but I think the recommended path is the most straightforward.
Open to other suggestions to get the token to the original caller though, @vyomshm, if you have a recommendation, as that is the goal.
It would be nice to use the latest Solidity version (currently v0.4.24). The more recent versions of Solidity introduce the constructor
keyword which could be used instead of a named constructor function like function MultiMerkleMine() public
in addition to the ability to attach a reason string when reverting either with the require
or revert
statements.
One thing to look out for is that as of Solidity v0.4.24, dynamic arrays of dynamic arrays parameters i.e. string[]
and bytes[]
are not supported by default. Some options to possibly consider here include:
pragma experimental ABIEncoderV2
multiGenerate()
, change bytes[] _merkleProofs
to bytes _merkleProofs
and include an additional parameter uint256[] _sizes
in multiGenerate
where _sizes[i]
is the size of the i
th Merkle proof. multiGenerate()
would use the values in _sizes
array to parse out each individual proof in _merkleProofs
multiGenerate()
, change bytes[] _merkleProofs
to bytes _merkleProofs
. The client can construct _merkleProofs
off-chain with the following format: [proof_1_size, proof_1, proof_2_size, proof_2, ... , proof_n_size, proof_n]
. multiGenerate()
would read the size of proof 1 in the first byte of _merkleProofs
, parse proof 1 from the next proof_1_size
bytes, read the size of proof 2 in the next byte, parse proof 2 from the next proof_2_size
bytes, and so on.IMO option 3 seems appealing and doesn't require using an experimental compiler feature, but open to other thoughts here too!
@vyomshm Hello from Gitcoin Core - are you still working on this issue? Please submit a WIP PR or comment back within the next 3 days or you will be removed from this ticket and it will be returned to an ‘Open’ status. Please let us know if you have questions!
Funders only: Snooze warnings for 1 day | 3 days | 5 days | 10 days | 100 days
@vyomshm are you still working on this? If not then I'd like to release it to someone else. Thanks, and let me know if you have any questions.
@dob @yondonfu
Yes, I'm still working on this.
Here's my current implementation of MultiMerkleMine.sol
-
pragma solidity ^0.4.24;
import "./MerkleMine.sol";
import "zeppelin-solidity/contracts/token/ERC20/ERC20.sol";
import "zeppelin-solidity/contracts/math/SafeMath.sol";
contract MultiMerkleMine {
using SafeMath for uint256;
bytes[] private _proofs;
event AlreadyGenerated(address indexed recipient, address indexed caller);
function extractProofs(bytes _merkleProofs) internal returns(bytes[]) {
require(_merkleProofs.length != 0, 'No proofs supplied!');
for(uint256 i=0; i<_merkleProofs.length; i++){
uint256 proofSize = uint256(_merkleProofs[i]);
require(proofSize % 32 == 0, 'Invalid proof detected!');
bytes memory _proof = new bytes(proofSize);
uint256 j = 0;
while(j<proofSize){
_proof[j]=_merkleProofs[i+1];
i+=1;
j+=1;
}
_proofs.push(_proof);
}
return _proofs;
}
function multiGenerate(address _merkleMineContract, address[] _recipients, bytes _merkleProofs) public {
MerkleMine mine = MerkleMine(_merkleMineContract);
ERC20 token = ERC20(mine.token());
require (block.number >= mine.callerAllocationStartBlock());
uint256 initialBalance = token.balanceOf(this);
bytes[] memory proofs = extractProofs(_merkleProofs);
require(proofs.length == _recipients.length, 'Number of recipients and proofs is not equal!');
for(uint256 i=0; i < _recipients.length; i++){
if(!mine.generated(_recipients[i])){
mine.generate(_recipients[i], proofs[i]);
}else{
emit AlreadyGenerated(_recipients[i], msg.sender);
}
}
uint256 newBalanceSinceAllocation = token.balanceOf(this);
uint256 callerTokensGenerated = newBalanceSinceAllocation.sub(initialBalance);
if(callerTokensGenerated > 0){
token.transfer(msg.sender, callerTokensGenerated);
}
delete _proofs;
}
}
I didn't want to end up using the experimental compiler for implementing this, so I went with @yondonfu 's suggestion. This implementation passes all the required tests but does include a storage variable! i'm still working on an alternative way to do this.
MultiMerkleMine
contract so that it uses assembly to extract proofs using a BytesUtil
library. Also it no longer has that bytes[] storage _proofs
array and everything happens in memory.Closed via #15
@vyomshm I'm trying to pay out the Gitcoin bounty, but it looks like it's still in "work started" phase. If you can mark it as work submitted, then I think I can pay it out.
Issue Status: 1. Open 2. Started 3. Submitted 4. Done
Work for 0.5 ETH (233.25 USD @ $466.5/ETH) has been submitted by:
@dob please take a look at the submitted work:
Yeah, sorry about that. I just did so, should be working now.
Issue Status: 1. Open 2. Started 3. Submitted 4. Done
The funding of 0.5 ETH (233.25 USD @ $466.5/ETH) attached to this issue has been approved & issued to @vyomshm.
Currently, the MerkleMine smart contract allows the caller to generate a single proof for a single recipient in the
generate()
transaction. This issue is about creating a new convenience smart contract called theMultiMerkleMine
which allows a caller to submit many proofs for many accounts in a single transaction. This allows a MerkleMiner to more efficiently generate token on unclaimed accounts while saving on gas.Description
The MultiMerkleMine contract is purely a convenience wrapper around an existing MerkleMine contract deployed on the blockchain.
generate()
function on an existing MerkleMine contract.generate()
function on the MerkleMine contract once for each unclaimed input recipient address/proof pair.MultiMerkleMine Spec
Constructor
function MultiMerkleMine() public
takes no arguments, as the contract stores no state
Public functions
Tests
Please write tests to cover the following cases:
Additional implementation details
MerkleTree
library to easily generate the data for test accounts and accompanying root + proofs.MerkleMine.callerAllocationStartBlock
. It is debateable whether this validation should be included or not, since the MerkleMine contract will just throw anyway early on if this time period has not begun. But keep in mind during testing that the current block needs to exceed the callerAllocationStartBlock for the submited proofs to actually validate.