Vend.sol (7205B)
1 pragma solidity ^0.8.0; 2 3 // Author: Louis Holbrook <dev@holbrook.no> 0826EDA1702D1E87C6E2875121D2E7BB88C2A746 4 // SPDX-License-Identifier: AGPL-3.0-or-later 5 // File-Version: 1 6 // Description: Create and vend ERC20 voting tokens in exchange for a held ERC20 token. 7 8 import "GiftableToken.sol"; 9 10 contract ERC20Vend { 11 address owner; 12 uint256 constant UINT256_MAX = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; 13 14 // Implements TokenSwap 15 address public defaultToken; 16 17 uint8 controlDecimals; 18 uint8 decimals; 19 uint256 supply; 20 uint256 decimalDivisor; 21 mapping ( address => uint256 ) returned; 22 GiftableToken[] vendToken; 23 mapping ( address => mapping ( address => uint256 ) ) used; 24 25 mapping(address => bool) writers; 26 27 event TokenCreated(uint256 indexed _idx, uint256 indexed _supply, address _token); 28 event Mint(address indexed _minter, address indexed _beneficiary, address indexed _token, uint256 value); 29 30 constructor(address _defaultToken, uint8 _decimals, bool _mint) { 31 bool r; 32 bytes memory v; 33 34 defaultToken = _defaultToken; 35 decimals = _decimals; 36 37 (r, v) = defaultToken.call(abi.encodeWithSignature("decimals()")); 38 require(r, "ERR_TOKEN"); 39 controlDecimals = abi.decode(v, (uint8)); 40 require(controlDecimals >= decimals); 41 42 if (!_mint) { 43 (r, v) = defaultToken.call(abi.encodeWithSignature("totalSupply()")); 44 require(r, "ERR_TOKEN"); 45 supply = abi.decode(v, (uint256)); 46 } 47 48 decimalDivisor = (10 ** (controlDecimals - _decimals)); 49 if (decimalDivisor == 0) { 50 decimalDivisor = 1; 51 } 52 owner = msg.sender; 53 } 54 55 // Implements Writer 56 function addWriter(address _minter) public returns (bool) { 57 require(msg.sender == owner); 58 writers[_minter] = true; 59 return true; 60 } 61 62 // Implements Writer 63 function deleteWriter(address _minter) public returns (bool) { 64 require(msg.sender == owner || msg.sender == _minter); 65 writers[_minter] = false; 66 return true; 67 } 68 69 // Implements Writer 70 function isWriter(address _minter) public view returns(bool) { 71 return writers[_minter] || _minter == owner; 72 } 73 74 // Retrieve address of vended token by sequential index. 75 function getTokenByIndex(uint256 _idx) public view returns(address) { 76 return address(vendToken[_idx]); 77 } 78 79 // Create a new vended token. 80 function create(string calldata _name, string calldata _symbol) public returns (address) { 81 GiftableToken l_contract; 82 address l_address; 83 uint256 l_idx; 84 85 l_contract = new GiftableToken(_name, _symbol, decimals, 0); 86 l_address = address(l_contract); 87 l_idx = vendToken.length; 88 vendToken.push(l_contract); 89 90 if (supply > 0) { 91 l_contract.mintTo(address(this), supply); 92 } 93 emit TokenCreated(l_idx, supply, l_address); 94 emit Mint(msg.sender, msg.sender, l_address, supply); 95 return l_address; 96 } 97 98 // Receive the vended token for the currently held balance. 99 function deposit(address _token, uint256 _value) public returns (uint256) { 100 GiftableToken l_token; 101 bool r; 102 bytes memory v; 103 uint256 l_ratioedValue; 104 uint256 l_controlBalance; 105 106 l_token = GiftableToken(_token); 107 108 require(used[msg.sender][address(_token)] == 0, "ERR_USED"); 109 110 if (_value > 0) { 111 l_controlBalance = _value; 112 } else { 113 (r, v) = defaultToken.call(abi.encodeWithSignature("balanceOf(address)", msg.sender)); 114 require(r, "ERR_TOKEN"); 115 l_controlBalance = abi.decode(v, (uint256)); 116 117 require(l_controlBalance < UINT256_MAX, "ERR_VALUE_TOO_HIGH"); 118 if (l_controlBalance == 0) { 119 return 0; 120 } 121 } 122 123 (r, v) = defaultToken.call(abi.encodeWithSignature("transferFrom(address,address,uint256)", msg.sender, this, l_controlBalance)); 124 require(r, "ERR_TOKEN"); 125 r = abi.decode(v, (bool)); 126 require(r, "ERR_TOKEN_TRANSFER"); 127 used[msg.sender][address(l_token)] = l_controlBalance; 128 129 if (decimalDivisor == 0) { 130 l_ratioedValue = l_controlBalance; 131 } else { 132 l_ratioedValue = l_controlBalance / decimalDivisor; 133 } 134 135 if (supply == 0) { 136 if (!l_token.mintTo(msg.sender, l_ratioedValue)) { 137 revert("ERR_MINT"); 138 } 139 } else { 140 (r, v) = _token.call(abi.encodeWithSignature("transfer(address,uint256)", msg.sender, l_ratioedValue)); 141 require(r, "ERR_TOKEN"); 142 r = abi.decode(v, (bool)); 143 require(r, "ERR_VEND_TOKEN_TRANSFER"); 144 } 145 return l_ratioedValue; 146 } 147 148 // If contract locks exchanged tokens, this can be called to retrieve the locked tokens. 149 // The vended token balance MUST match the original balance emitted on the exchange. 150 // The caller must have given allowance for the full amount. 151 function withdraw(address _token, uint256 _value) public returns (uint256) { 152 bool r; 153 bytes memory v; 154 uint256 l_balance; 155 uint256 l_vendBalance; 156 157 if (_token == defaultToken) { 158 return deposit(_token, _value); 159 } 160 161 l_balance = used[msg.sender][_token]; 162 if (l_balance == 0) { 163 return 0; 164 } 165 require(used[msg.sender][_token] < UINT256_MAX, "ERR_ALREADY_WITHDRAWN"); 166 l_vendBalance = checkLock(_token, msg.sender); 167 used[msg.sender][_token] = UINT256_MAX; 168 169 if (l_vendBalance == UINT256_MAX) { 170 return 0; 171 } 172 173 if (_value > 0) { 174 require(l_vendBalance == _value, "ERR_VALUE_MISMATCH"); 175 } 176 177 (r, v) = _token.call(abi.encodeWithSignature("transferFrom(address,address,uint256)", msg.sender, this, l_vendBalance)); 178 require(r, "ERR_TOKEN"); 179 r = abi.decode(v, (bool)); 180 require(r, "ERR_TOKEN_TRANSFER"); 181 returned[_token] += l_vendBalance; 182 183 (r, v) = defaultToken.call(abi.encodeWithSignature("transfer(address,uint256)", msg.sender, l_balance)); 184 require(r, "ERR_TOKEN"); 185 r = abi.decode(v, (bool)); 186 require(r, "ERR_TOKEN_TRANSFER"); 187 188 return l_balance; 189 } 190 191 // Implements TokenSwap 192 // Will always revert, as sync swap is not valid in this implementation. 193 function withdraw(address _outToken, address _inToken, uint256 _value) public pure returns (uint256) { 194 _outToken; 195 _inToken; 196 _value; 197 require(1 == 0, "ERR_NO_SYNC_SWAP"); 198 return 0; 199 } 200 201 202 // // burn used vend tokens. 203 // // should self-destruct contract if possible when supply reaches 0. 204 // function burnFor(address _token) public returns(uint256) { 205 // bool r; 206 // bytes memory v; 207 // uint256 l_burnValue; 208 // 209 // l_burnValue = returned[_token]; 210 // (r, v) = _token.call(abi.encodeWithSignature("burn(uint256)", l_burnValue)); 211 // require(r, "ERR_TOKEN"); 212 // r = abi.decode(v, (bool)); 213 // require(r, "ERR_TOKEN_BURN"); 214 // returned[_token] = 0; 215 // return l_burnValue; 216 // } 217 218 // returns UINT256_MAX if lock is inactive 219 // reverts if target does not have the original balance 220 function checkLock(address _token, address _target) private returns (uint256) { 221 bool r; 222 bytes memory v; 223 uint256 l_currentBalance; 224 uint256 l_heldBalance; 225 226 (r, v) = _token.call(abi.encodeWithSignature("balanceOf(address)", _target)); 227 require(r, "ERR_TOKEN"); 228 l_currentBalance = abi.decode(v, (uint256)); 229 l_heldBalance = used[_target][_token]; 230 require(l_currentBalance * decimalDivisor == l_heldBalance, "ERR_LOCKED"); 231 return l_currentBalance; 232 } 233 234 // Implements EIP165 235 function supportsInterface(bytes4 _sum) public pure returns (bool) { 236 if (_sum == 0x01ffc9a7) { // EIP165 237 return true; 238 } 239 if (_sum == 0xabe1f1f5) { // Writer 240 return true; 241 } 242 if (_sum == 0x4146b765) { // TokenSwap 243 return true; 244 } 245 return false; 246 } 247 }