erc20-vend

Create ERC20 tokens the can be minted by existing ERC20 token balance
Log | Files | Refs

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 }