diff --git a/contracts/bank/IssueBank.sol b/contracts/bank/IssueBank.sol new file mode 100644 index 0000000..46d4d0a --- /dev/null +++ b/contracts/bank/IssueBank.sol @@ -0,0 +1,222 @@ +import "../bank/TokenBank.sol"; +import "../management/Controlled.sol"; + +pragma solidity ^0.4.11; + +/** + * @title IssueBank + * @author Ricardo Guilherme Schmidt <3esmit> + * Enable deposits to be withdrawn by points recievers + **/ +contract IssueBank is Controlled, TokenBank { + enum State {OPEN, REFUND, REWARD, FINALIZED } + State public state; + uint public points; + uint public refundNonce; + mapping (address => Reward) public rewards; + address public repoOwner; + + struct Reward { + bool active; + uint points; + } + + modifier onlyRepoOwner{ + if (msg.sender != repoOwner) throw; + _; + } + + function IssueBank(address _repoOwner) { + repoOwner = _repoOwner; + state = State.OPEN; + } + + /** + * @notice deposit ether in bank + **/ + function () payable { + deposit(); + } + + /** + * @notice claim all watched tokens based on user points; + */ + function reward() { + _reward(tokens); + } + + /** + * @notice Useful if some token is throwing on transfer. + * @param _tokens the array of desired watched tokens. If token is not watched it will ignore.. + */ + function rewardEthAnd(address[] _tokens) { + _reward(_tokens); + } + /** + * @notice only contoller may set reward to a single address. + * @param _claimer the beneficiary + * @param _points amount of points + **/ + function setReward(address _claimer, uint _points) onlyController { + if(state != State.OPEN) throw; + if(rewards[_claimer].active) throw; + rewards[_claimer].points = _points; + } + + /** + * @notice only contoller may set reward to an array of addresses. + * @param _claimers the array of beneficiaries + * @param _points the array of amount of points + **/ + function setReward(address[] _claimers, uint[] _points) onlyController { + if(state != State.OPEN) throw; + uint len = _claimers.length; + for (uint i = 0; i < len; i++){ + address _claimer = _claimers[i]; + if(rewards[_claimer].active) throw; + rewards[_claimer].points = _points[i]; + } + } + + /** + * @notice only the repo owner may confirm reward of addresses + * @param _claimers array of addresses that are eligible to reward + **/ + function confirm(address [] _claimers) onlyRepoOwner { + uint len = _claimers.length; + uint nPoints = 0; + for (uint i = 0; i < len; i++){ + address _claimer = _claimers[i]; + if(rewards[_claimer].active) throw; + rewards[_claimer].active = true; + nPoints += rewards[_claimer].points; + } + points += nPoints; + } + + /** + * @notice only the repo owner may confirm reward of single address + * @param _claimer the address that is eligible to reward + **/ + function confirm(address _claimer) onlyRepoOwner { + if(rewards[_claimer].active) throw; + rewards[_claimer].active = true; + points += rewards[_claimer].points; + } + + /** + * @notice only repo owner can close deposits and start reward or refund + * If no points confirmed the system will start refund, otherwise reward + */ + function close() onlyRepoOwner { + if(state != State.OPEN) throw; + state = points > 0 ? State.REWARD : State.REFUND; + } + + /** + * @notice Set a new list of tokens to be rewarded. + * User can still reward tokens not listed if he calls `rewardEthAnd(address[]` + * providing a list including unlisted tokens. + * To list a new token use `watch(address)` that will update the balance aswell + * @param _tokens the list of new tokens. + * + */ + function setTokenList(address[] _tokens) onlyController { + tokens = _tokens; + } + + /** + * @notice Withdraw tokens + * only avaliable in REWARD state and all points claimed. + * + **/ + function withdraw(address[] _tokens) onlyController { + if(state != State.FINALIZED) throw; + uint len = _tokens.length; + uint amount; + for (uint i = 0; i< len; i++){ + ERC20 token = ERC20(_tokens[i]); + amount = token.balanceOf(this); + if(amount > 0) withdraw(token, repoOwner, amount); + } + } + /** + * @notice only controller may kill contract and send all remaining eth and tokens to repo owner + * This can only be done when all rewards are claimed. + **/ + function kill() onlyController { + if(state != State.FINALIZED) throw; + withdraw(tokens); + selfdestruct(repoOwner); + } + /** + * @dev overwriten to only allow refund in correct state. + **/ + function refund(uint depositNonce) returns (bool) { + if(state != State.REFUND) throw; + refundNonce++; + if(refundNonce == nonce) state = State.FINALIZED; + return super.refund(depositNonce); + } + + /** + * @dev register the deposit to refundings + **/ + function _deposited(address _tokenAddr, address _sender, uint _amount) + internal returns (uint receipt) { + if(state != State.OPEN) throw; + return super._deposited(_tokenAddr, _sender, _amount); + } + + function _reward(address[] _tokens) internal { + if(state != State.REWARD) throw; + address dest = msg.sender; + if (!rewards[dest].active) throw; + uint _reward_points = rewards[dest].points; + delete rewards[dest]; + if (_reward_points == 0) throw; + uint reward; + uint len = _tokens.length; + for (uint i = 0; i< len; i++){ + address tokenAddr = _tokens[i]; + reward = tokenBalances[tokenAddr]; + if (reward > 0) reward = calculeReward(reward, _reward_points); + if (reward > 0) withdraw(tokenAddr, dest, reward); + } + reward = this.balance; + if (reward > 0) reward = (reward / points) * _reward_points; + if (reward > 0) withdraw(dest, reward); + points -= _reward_points; + if(points == 0) state = State.FINALIZED; + } + + /** + * @dev amplifies small token balances to divide points + **/ + function calculeReward(uint _balance, uint _reward_points) internal constant returns (uint reward) { + uint amplifier = 1; + while(_balance * amplifier < points){ + amplifier *= 10; + } + reward = (((_balance*amplifier) / points) * _reward_points) / amplifier; + } + + +} + +/** + * @title IssueBankFactory + * @author Ricado Guilherme Schmidt <3esmit> + **/ +contract IssueBankFactory { + + /** + * @notice creates new IssueBank with repoOwner as moderator + **/ + function create(address repoOwner) returns(IssueBank){ + IssueBank bank = new IssueBank(repoOwner); + bank.changeController(msg.sender); + return bank; + } + +} \ No newline at end of file diff --git a/contracts/bank/TokenBank.sol b/contracts/bank/TokenBank.sol new file mode 100644 index 0000000..41abb0d --- /dev/null +++ b/contracts/bank/TokenBank.sol @@ -0,0 +1,154 @@ +import "../token/ERC23.sol"; + +pragma solidity ^0.4.11; + +/** + * @title TokenBank + * @author Ricardo Guilherme Schmidt <3esmit> + * Abstract contract for deposit and withdraw of ETH and ERC20/23 Tokens + **/ +contract TokenBank is ERC23Receiver, ApproveAndCallFallBack { + + event Withdrawn(address reciever, uint amount); + event Deposited(address sender, uint value); + event TokenWithdrawn(address token, address reciever, uint amount); + event TokenDeposited(address token, address sender, uint value); + + mapping (uint => Deposit) public deposits; + mapping (address => uint) public tokenBalances; + address[] public tokens; + + uint public nonce; + + struct Deposit { + address sender; + address token; + uint amount; + } + + /** + * @notice deposit ether in bank + * @return reciept that can be used for refund + **/ + function deposit() payable returns (uint receipt) { + address sender = msg.sender; + uint amount = msg.value; + if(amount > 0){ + return _deposited(0x0, sender, amount); + } + } + + /** + * @notice deposit a ERC20 token. The amount of deposit is the allowance set to this contract. + * @param _tokenAddr the token contract address + * @return reciept that can be used for refund + **/ + function tokenDeposit(address _tokenAddr) returns (uint reciept) { + address sender = msg.sender; + ERC20 token = ERC20(_tokenAddr); + uint amount = token.allowance(sender, this); + if(amount == 0) throw; + uint _nonce = nonce; + if(!token.transferFrom(sender, this, amount)) throw; + if(!token.approve(this, amount)) throw; + if(nonce == _nonce){ + reciept = _deposited(_tokenAddr, sender, amount); + }else{ + reciept = _nonce; //ERC23 executed _deposited tokenFallback by + } + } + + /** + * @notice watches for balance in a token contract + * @param _tokenAddr the token contract address + **/ + function watch(address _tokenAddr){ + bool neverSeen = false; + if(tokenBalances[_tokenAddr] == 0) neverSeen = true; + uint amount = ERC20(_tokenAddr).balanceOf(this); + if(amount > 0){ + if(!ERC20(_tokenAddr).approve(this, amount)) throw; + if(neverSeen) tokens.push(_tokenAddr); + tokenBalances[_tokenAddr] = amount; + } + } + + /** + * @notice refunds a deposit. + * @param _nonce the reciept you want to refund + **/ + function refund(uint _nonce) returns (bool) { + if(msg.sender != deposits[_nonce].sender) throw; + uint amount = deposits[_nonce].amount; + address token = deposits[_nonce].token; + delete deposits[_nonce]; + if(token == 0x0){ + withdraw(msg.sender,amount); + } else { + withdraw(ERC20(token), msg.sender, amount); + } + return true; + } + + /** + * @notice ERC23 Token fallback + * @param _from address incoming token + * @param _amount incoming amount + **/ + function tokenFallback(address _from, uint _amount, bytes) { + _deposited(msg.sender, _from, _amount); + } + + /** + * @notice Called MiniMeToken approvesAndCall to this contract + * @param _from address incoming token + * @param _amount incoming amount + * @param _token the token contract address + */ + function receiveApproval(address _from, uint256 _amount, address _token, bytes){ + _deposited(_token, _from, _amount); + } + + + /** + * @dev register the deposit to refundings + **/ + function _deposited(address _tokenAddr, address _sender, uint _amount) + internal returns (uint receipt) { + if(_tokenAddr != 0x0){ + TokenDeposited(_tokenAddr, _sender, _amount); + if(tokenBalances[_tokenAddr] == 0){ + tokens.push(_tokenAddr); + tokenBalances[_tokenAddr] = ERC20(_tokenAddr).balanceOf(this); + }else{ + tokenBalances[_tokenAddr] += _amount; + } + }else{ + Deposited(_sender, _amount); + } + receipt = nonce; + nonce++; + deposits[receipt] = Deposit({sender: _sender, amount: _amount, token: _tokenAddr}); + } + + + /** + * @dev withdraw amount wei to dest + **/ + function withdraw(address _dest, uint _amount) + internal { + _dest.transfer(_amount); + Withdrawn(msg.sender, _amount); + } + + /** + * @dev withdraw token amount to dest + **/ + function withdraw(address _tokenAddr, address _dest, uint _amount) + internal { + if(!ERC20(_tokenAddr).transferFrom(this, _dest, _amount)) throw; + tokenBalances[_tokenAddr] -= _amount; + TokenWithdrawn(_tokenAddr, _dest, _amount); + } + +}