erc20-transfer-authorization

Simple approval escrow for ERC20 spending
Info | Log | Files | Refs

ERC20TransferAuthorization.sol (6930B)


      1 pragma solidity >0.8.0;
      2 
      3 // SPDX-License-Identifier: GPL-3.0-or-later
      4 
      5 contract ERC20TransferAuthorization {
      6 	struct Transaction {
      7 		uint256 value;
      8 		address sender;
      9 		address recipient;
     10 		address token;
     11 		uint32 serial;
     12 		uint32 yay;
     13 		uint32 nay;
     14 		//uint256 blockNumber;
     15 		// bit 1: started
     16 		// bit 2: approved
     17 		// bit 3: rejected
     18 		// bit 4: finalized
     19 		// bit 5: transfererror
     20 		uint8 result;
     21 	}
     22 
     23 	mapping ( uint32 => mapping ( address => int8 )) public vote;
     24 	//mapping ( uint256 => address[] ) public voters;
     25 	mapping( uint32 => Transaction ) public requests;
     26 	//mapping(address => uint256[]) public requestSenderIndex;
     27 	//mapping(address => uint256[]) public requestRecipientIndex;
     28 	address public owner;
     29 	uint32 hi;
     30 	uint32 lo;
     31 	uint32 public count;
     32 	uint32 public quorum;
     33 	uint32 public vetoThreshold;
     34 	uint32 public signerCount;
     35 
     36 	mapping(address => bool) public signers;
     37 	address[] public writers;
     38 
     39 	event NewRequest(address indexed _sender, address indexed _recipient, address indexed _token, uint256 _value, uint32 _serial);
     40 	event Executed(uint32 _serial);
     41 	event TransferFail(uint32 _serial);
     42 	event QuorumSet(uint32 indexed _quorum, uint32 indexed _vetoThreshold, uint32 indexed _signerCount);
     43 	event WriterAdded(address _signer);
     44 	event WriterRemoved(address _signer);
     45 	event Vetoed(uint32 indexed _serial, uint32 indexed _yays, uint32 indexed _nays);
     46 	event Approved(uint32 indexed _serial, uint32 indexed _yays, uint32 indexed _nays);
     47 	event Rejected(uint32 indexed _serial, uint32 indexed _yays, uint32 indexed _nays);
     48 
     49 	constructor() {
     50 		owner = msg.sender;
     51 		hi = 1;
     52 		lo = 1;
     53 		addWriter(msg.sender);
     54 		setThresholds(1, 0);
     55 	}
     56 
     57 	function isWriter(address _signer) public view returns (bool) {
     58 		return signers[_signer];
     59 	}
     60 
     61 	function addWriter(address _signer) public returns (uint32) {
     62 		require(msg.sender == owner, 'ERR_ACCESS');
     63 		require(signers[_signer] == false, 'ERR_NOTFOUND');
     64 
     65 		signers[_signer] = true;
     66 		signerCount++;
     67 		emit WriterAdded(_signer);
     68 		return signerCount;
     69 	}
     70 
     71 	function removeWriter(address _signer) public returns (uint32) {
     72 		//require(msg.sender == owner || msg.sender == _signer, 'ERR_ACCESS');
     73 		require(msg.sender == owner, 'ERR_ACCESS');
     74 		require(signers[_signer] == true, 'ERR_NOTFOUND');
     75 		require(signerCount > quorum && signerCount > vetoThreshold, 'ERR_REDUCE_THRESHOLD_FIRST');
     76 
     77 		signers[_signer] = false;
     78 		signerCount--;
     79 		emit WriterRemoved(_signer);
     80 		return signerCount;
     81 	}
     82 
     83 	function setThresholds(uint32 _quorum, uint32 _vetoThreshold) public returns (bool) {
     84 		require(_quorum <= signerCount);
     85 		require(_quorum > 0);
     86 		require(_vetoThreshold <= signerCount);
     87 
     88 		quorum = _quorum;
     89 		vetoThreshold = _vetoThreshold;
     90 		emit QuorumSet(quorum, vetoThreshold, signerCount);
     91 		return true;
     92 	}
     93 
     94 	// create new request
     95 	function createRequest(address _sender, address _recipient, address _token, uint256 _value) public returns (uint32) {
     96 		Transaction storage txx = requests[hi];
     97 
     98 		txx.serial = hi;
     99 		txx.recipient = _recipient;
    100 		txx.sender = _sender;
    101 		txx.token = _token;
    102 		txx.value = _value;
    103 		txx.result = 1;
    104 
    105 		count++;
    106 		hi++;
    107 
    108 		emit NewRequest(txx.sender, txx.recipient, txx.token, txx.value, txx.serial);
    109 
    110 		return txx.serial;
    111 	}
    112 
    113 	// if request was oldest in index, move the pointer to oldest request to next oldest unfinished request.
    114 	// if no unfinished requests exits, it will point to newest request
    115 	function removeItem(uint32 _serialToRemove) private returns (uint32) {
    116 		count--;
    117 
    118 		if (count > 0) {
    119 			if (_serialToRemove == lo) {
    120 				uint32 i;
    121 				i = getSerialAt(0);
    122 				if (i == 0) {
    123 					lo = hi;
    124 				} else {
    125 					lo = i;
    126 				}
    127 			}
    128 		} else if (lo != hi) {
    129 			lo = hi;
    130 		}
    131 
    132 		return lo;
    133 	}
    134 
    135 	// index of newest vote
    136 	function lastSerial() public view returns ( uint32 ) {
    137 		return hi - 1;
    138 	}
    139 
    140 	// index of oldest unfinished vote
    141 	function nextSerial() public view returns ( uint32 ) {
    142 		if (hi - lo == 0) {
    143 			if (hi == 1) {
    144 				return 0;
    145 			}
    146 			Transaction storage txx = requests[lo];
    147 			if (txx.result > 0) {
    148 				return lo;
    149 			}
    150 			return 0;
    151 		}
    152 		return lo;
    153 	}
    154 
    155 	// get the nth unfinished vote, where nth is _idx, starting at 0
    156 	function getSerialAt(uint32 _idx) public view returns ( uint32 ) {
    157 		uint32 i;
    158 
    159 		if (lo == hi) {
    160 			return 0;
    161 		}
    162 
    163 		for (uint32 j = lo; j < hi; j++) {
    164 			Transaction storage txx = requests[j];
    165 			if (txx.result & 7 == 1) {
    166 				if (i == _idx) {
    167 					return txx.serial;
    168 				}
    169 				i++;
    170 			}
    171 		}
    172 		return 0;
    173 	}
    174 
    175 	// vote yay, one per signer
    176 	function yay(uint32 _serial) public returns (uint32) {
    177 		require(signers[msg.sender], 'ERR_ACCESS');
    178 		require(vote[_serial][msg.sender] == 0, 'ERR_ALREADYVOTED');
    179 
    180 		Transaction storage txx = requests[_serial];
    181 		require(txx.result == 1);
    182 
    183 		vote[txx.serial][msg.sender] = 1;
    184 		//voters[txx.serial].push(msg.sender);
    185 		txx.yay++;
    186 
    187 		checkResult(txx.serial);	
    188 
    189 		return txx.yay;
    190 	}
    191 
    192 	// vote nay, one per signer
    193 	function nay(uint32 _serial) public returns (uint32) {
    194 		require(signers[msg.sender], 'ERR_ACCESS');
    195 		require(vote[_serial][msg.sender] == 0, 'ERR_ALREADYVOTED');
    196 
    197 		Transaction storage txx = requests[_serial];
    198 		require(txx.result == 1);
    199 
    200 		vote[txx.serial][msg.sender] = -1;
    201 		//voters[txx.serial].push(msg.sender);
    202 		txx.nay++;
    203 
    204 		checkResult(txx.serial);	
    205 
    206 		return txx.nay;
    207 	}
    208 
    209 	// locks the state of the vote if quorum or veto is reached
    210 	// returns true if state changes
    211 	function checkResult(uint32 _serial) public returns (bool) {
    212 		bool result;
    213 
    214 		Transaction storage txx = requests[_serial];
    215 
    216 		if (txx.result < 1 || txx.result & 6 > 0) {
    217 			return result;
    218 		}
    219 
    220 		if (txx.yay >= quorum) {
    221 			txx.result |= 2;
    222 			emit Approved(txx.serial, txx.yay, txx.nay);
    223 			result = true;
    224 		} else if (vetoThreshold > 0 && txx.nay >= vetoThreshold) {
    225 			txx.result |= 4;
    226 			removeItem(txx.serial);
    227 			emit Vetoed(txx.serial, txx.yay, txx.nay);
    228 			result = true;
    229 		} else if (signerCount - txx.nay < quorum) { 
    230 			txx.result |= 4;
    231 			removeItem(txx.serial);
    232 			emit Rejected(txx.serial, txx.yay, txx.nay);
    233 			result = true;
    234 		}
    235 
    236 		return result;
    237 	}
    238 
    239 	// execute transfer. needs positive vote result
    240 	function executeRequest(uint32 _serial) public returns (bool) {
    241 		Transaction storage txx = requests[_serial];
    242 
    243 		require(txx.serial > 0, 'ERR_INVALID_REQUEST');
    244 		require(txx.result & 11 == 3, 'ERR_NOT_ENDORSED');
    245 
    246 		removeItem(txx.serial);
    247 		txx.result |= 8;
    248 
    249 		(bool success, bytes memory _r) = txx.token.call(abi.encodeWithSignature("transferFrom(address,address,uint256)", txx.sender, txx.recipient, txx.value));
    250 
    251 		//txx.blockNumber = block.number;
    252 		//requestSenderIndex[txx.sender].push(txx.serial);
    253 		//requestRecipientIndex[txx.recipient].push(txx.serial);
    254 		if (!success) {
    255 			revert('ERR_TRANSFER_FAIL');
    256 		}
    257 		if (_r[31] == 0x01) {
    258 			emit Executed(_serial);
    259 		} else {
    260 			txx.result |= 16; // this edit is for convenience only. since bit 4 is already set, it is not re-entrant.
    261 			emit TransferFail(_serial);
    262 		}
    263 
    264 		return success;
    265 	}
    266 }