Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
138 lines (109 sloc)
4.89 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Please read and review the Terms and Conditions governing this | |
Merkle Drop by visiting the Trustlines Foundation homepage. Any | |
interaction with this smart contract, including but not limited to | |
claiming Trustlines Network Tokens, is subject to these Terms and | |
Conditions. | |
*/ | |
pragma solidity ^0.5.8; | |
import "./ERC20Interface.sol"; | |
contract MerkleDrop { | |
bytes32 public root; | |
ERC20Interface public droppedToken; | |
uint public decayStartTime; | |
uint public decayDurationInSeconds; | |
uint public initialBalance; | |
uint public remainingValue; // The total of not withdrawn entitlements, not considering decay | |
uint public spentTokens; // The total tokens spent by the contract, burnt or withdrawn | |
mapping (address => bool) public withdrawn; | |
event Withdraw(address recipient, uint value, uint originalValue); | |
event Burn(uint value); | |
constructor(ERC20Interface _droppedToken, uint _initialBalance, bytes32 _root, uint _decayStartTime, uint _decayDurationInSeconds) public { | |
// The _initialBalance should be equal to the sum of airdropped tokens | |
droppedToken = _droppedToken; | |
initialBalance = _initialBalance; | |
remainingValue = _initialBalance; | |
root = _root; | |
decayStartTime = _decayStartTime; | |
decayDurationInSeconds = _decayDurationInSeconds; | |
} | |
function withdraw(uint value, bytes32[] memory proof) public { | |
require(verifyEntitled(msg.sender, value, proof), "The proof could not be verified."); | |
require(! withdrawn[msg.sender], "You have already withdrawn your entitled token."); | |
burnUnusableTokens(); | |
uint valueToSend = decayedEntitlementAtTime(value, now, false); | |
assert(valueToSend <= value); | |
require(droppedToken.balanceOf(address(this)) >= valueToSend, "The MerkleDrop does not have tokens to drop yet / anymore."); | |
require(valueToSend != 0, "The decayed entitled value is now zero."); | |
withdrawn[msg.sender] = true; | |
remainingValue -= value; | |
spentTokens += valueToSend; | |
require(droppedToken.transfer(msg.sender, valueToSend)); | |
emit Withdraw(msg.sender, valueToSend, value); | |
} | |
function verifyEntitled(address recipient, uint value, bytes32[] memory proof) public view returns (bool) { | |
// We need to pack the 20 bytes address to the 32 bytes value | |
// to match with the proof made with the python merkle-drop package | |
bytes32 leaf = keccak256(abi.encodePacked(recipient, value)); | |
return verifyProof(leaf, proof); | |
} | |
function decayedEntitlementAtTime(uint value, uint time, bool roundUp) public view returns (uint) { | |
if (time <= decayStartTime) { | |
return value; | |
} else if (time >= decayStartTime + decayDurationInSeconds) { | |
return 0; | |
} else { | |
uint timeDecayed = time - decayStartTime; | |
uint valueDecay = decay(value, timeDecayed, decayDurationInSeconds, !roundUp); | |
assert(valueDecay <= value); | |
return value - valueDecay; | |
} | |
} | |
function burnUnusableTokens() public { | |
if (now <= decayStartTime) { | |
return; | |
} | |
// The amount of tokens that should be held within the contract after burning | |
uint targetBalance = decayedEntitlementAtTime(remainingValue, now, true); | |
// toBurn = (initial balance - target balance) - what we already removed from initial balance | |
uint currentBalance = initialBalance - spentTokens; | |
assert(targetBalance <= currentBalance); | |
uint toBurn = currentBalance - targetBalance; | |
spentTokens += toBurn; | |
burn(toBurn); | |
} | |
function deleteContract() public { | |
require(now >= decayStartTime + decayDurationInSeconds, "The storage cannot be deleted before the end of the merkle drop."); | |
burnUnusableTokens(); | |
selfdestruct(address(0)); | |
} | |
function verifyProof(bytes32 leaf, bytes32[] memory proof) internal view returns (bool) { | |
bytes32 currentHash = leaf; | |
for (uint i = 0; i < proof.length; i += 1) { | |
currentHash = parentHash(currentHash, proof[i]); | |
} | |
return currentHash == root; | |
} | |
function parentHash(bytes32 a, bytes32 b) internal pure returns (bytes32) { | |
if (a < b) { | |
return keccak256(abi.encode(a, b)); | |
} else { | |
return keccak256(abi.encode(b, a)); | |
} | |
} | |
function burn(uint value) internal { | |
if (value == 0) { | |
return; | |
} | |
emit Burn(value); | |
droppedToken.burn(value); | |
} | |
function decay(uint value, uint timeToDecay, uint totalDecayTime, bool roundUp) internal pure returns (uint) { | |
uint decay; | |
if (roundUp) { | |
decay = (value*timeToDecay+totalDecayTime-1)/totalDecayTime; | |
} else { | |
decay = value*timeToDecay/totalDecayTime; | |
} | |
return decay >= value ? value : decay; | |
} | |
} |