evm-booking

EVM smart contract for ERC20 backed time slot booking
Info | Log | Files | Refs | README

Booking.sol (6934B)


      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: ERC20 backed time slot booking
      7 
      8 
      9 contract ERC20Book {
     10 	address public owner;
     11 	address public token;
     12 	bytes slots;
     13 	bytes sharedSlots;
     14 	uint256 public capacity;
     15 	uint256 public totalSupply;
     16 	uint256 public originalTokenSupply;
     17 	uint256 public shareCount;
     18 	mapping ( address => uint256 ) shares;
     19 	mapping ( address => bool ) writers;
     20 	uint256 public expires;
     21 	bool expired;
     22 
     23 	// Implements Seal
     24 	event SealStateChange(bool indexed _final, uint256 _sealState);
     25 
     26 	// Implements Seal
     27 	uint256 public sealState;
     28 	uint8 constant WRITER_STATE = 1;
     29 	uint8 constant SHARE_STATE = 2;
     30 	uint256 constant public maxSealState = 3;
     31 
     32 	// Implements Expire
     33 	event Expired(uint256 _timestamp);
     34 
     35 	constructor (address _token, uint256 _resolution, uint256 _expireTimestamp) {
     36 		require(_resolution > 0 && _resolution < (1 << 128), "ERR_NONSENSE");
     37 		require(_expireTimestamp > block.timestamp, "ERR_PAST");
     38 
     39 		uint256[2] memory r;
     40 
     41 		r = getPos(_resolution);
     42 		slots = new bytes(r[0] + 1);
     43 		sharedSlots = new bytes(r[0] + 1);
     44 		capacity = _resolution;
     45 		totalSupply = capacity;
     46 		token = _token;
     47 		originalTokenSupply = tokenSupply();
     48 		owner = msg.sender;
     49 		expires = _expireTimestamp;
     50 	}
     51 
     52 	// Implements Seal
     53 	function seal(uint256 _state) public returns(uint256) {
     54 		require(_state < maxSealState + 1, 'ERR_INVALID_STATE');
     55 		require(_state & sealState == 0, 'ERR_ALREADY_LOCKED');
     56 		sealState |= _state;
     57 		emit SealStateChange(sealState == maxSealState, sealState);
     58 		return uint256(sealState);
     59 	}
     60 
     61 	// Implements Seal
     62 	function isSealed(uint256 _state) public view returns(bool) {
     63 		require(_state < maxSealState);
     64 		if (_state == 0) {
     65 			return sealState == maxSealState;
     66 		}
     67 		return _state & sealState == _state;
     68 	}
     69 
     70 	// Implements Writer
     71 	function addWriter(address _writer) public returns (bool) {
     72 		require(!isSealed(WRITER_STATE), "ERR_SEALED");
     73 		require(msg.sender == owner, "ERR_AXX");
     74 		writers[_writer] = true;
     75 		return true;
     76 	}
     77 
     78 	// Implements Writer
     79 	function removeWriter(address _writer) public returns (bool) {
     80 		require(!isSealed(WRITER_STATE), "ERR_SEALED");
     81 		require(msg.sender == owner || msg.sender == _writer, "ERR_AXX");
     82 		writers[_writer] = false;
     83 		return true;
     84 	}
     85 
     86 	// Implements Writer
     87 	function isWriter(address _writer) public view returns (bool) {
     88 		return writers[_writer] || _writer == owner;
     89 	}
     90 
     91 	function depositFor(address _spender) public returns (int256) {
     92 		uint256 l_limit;
     93 		uint256 l_unit;
     94 		int256 l_value;
     95 		bool r;
     96 		bytes memory v;
     97 		address l_sender;
     98 		address l_receiver;
     99 
    100 		applyExpiry();
    101 		if (expired) {
    102 			return 0;
    103 		}
    104 
    105 		l_unit = unitValue();	
    106 		//return shareCount * l_unit;
    107 		l_limit = shareCount * l_unit;
    108 		if (l_limit == shares[_spender]) {
    109 			return 0;
    110 		}
    111 
    112 		l_value = int256(l_limit) - int256(shares[_spender]);
    113 		if (l_limit > shares[_spender]) {
    114 			l_sender = _spender;
    115 			l_receiver = address(this);
    116 		} else {
    117 			l_sender = address(this);
    118 			l_receiver = _spender;
    119 			l_value *= -1;
    120 		}
    121 
    122 		(r, v) = token.call(abi.encodeWithSignature('transferFrom(address,address,uint256)', l_sender, l_receiver, uint256(l_value)));
    123 		require(r, "ERR_TOKEN");
    124 		r = abi.decode(v, (bool));
    125 		require(r, "ERR_TRANSFER");
    126 
    127 		shares[_spender] = l_limit;
    128 		return l_value;
    129 	}
    130 
    131 	function deposit() public returns(int256) {
    132 		return depositFor(msg.sender);
    133 	}
    134 
    135 	function unitValue() internal returns(uint256) {
    136 		uint256 l_supply;
    137 		uint256 l_unit;
    138 
    139 		l_supply = tokenSupply();
    140 		require(l_supply == originalTokenSupply, "ERR_SUPPLY_CHANGED");
    141 		require(l_supply >= totalSupply, "ERR_SUPPLY_UNDERFLOW");
    142 		l_unit = l_supply / totalSupply;
    143 		return l_unit;
    144 	}
    145 
    146 	function tokenSupply() internal returns (uint256) {
    147 		bool r;
    148 		bytes memory v;
    149 		uint256 l_supply;
    150 
    151 		(r, v) = token.call(abi.encodeWithSignature('totalSupply()'));
    152 		require(r, "ERR_TOKEN");
    153 		l_supply = abi.decode(v, (uint256));
    154 
    155 		return l_supply;
    156 	}
    157 
    158 	function consume(uint256 _offset, uint256 _count) public returns(bool) {
    159 		bool r;
    160 		bytes memory v;
    161 		uint256 l_value;
    162 
    163 		applyExpiry();
    164 		if (expired) {
    165 			return false;
    166 		}
    167 
    168 		l_value = unitValue() * _count;
    169 		(r, v) = token.call(abi.encodeWithSignature('transferFrom(address,address,uint256)', msg.sender, this, l_value));
    170 		require(r, "ERR_TOKEN");
    171 		r = abi.decode(v, (bool));
    172 		require(r, "ERR_TRANSFER");
    173 
    174 		reserve(_offset, _count, false);
    175 		return true;
    176 	}
    177 
    178 	function share(uint256 _offset, uint256 _count) public returns(bool) {
    179 		require(!isSealed(SHARE_STATE), "ERR_SEALED");
    180 		require(isWriter(msg.sender), "ERR_AXX");
    181 
    182 		applyExpiry();
    183 		if (expired) {
    184 			return false;
    185 		}
    186 		reserve(_offset, _count, true);
    187 		return true;
    188 	}
    189 
    190 	// improve by comparing word by word
    191 	function reserve(uint256 _offset, uint256 _count, bool _share) internal {
    192 		require(_count > 0, "ERR_ZEROCOUNT");
    193 		uint256[2] memory c;
    194 		uint256 cy;
    195 		uint8 ci;
    196 
    197 		c = getPos(_offset);
    198 		cy = c[0];
    199 		ci = uint8(1 << (uint8(c[1])));
    200 		for (uint256 i = 0; i < _count; i++) {
    201 			require(capacity > 0, "ERR_CAPACITY");
    202 			require(slotAvailable(cy, ci), "ERR_COLLISION");
    203 			if (_share) {
    204 				sharedSlots[cy] = bytes1(uint8(sharedSlots[cy]) | ci);
    205 				shareCount++;
    206 			} else {
    207 				slots[cy] = bytes1(uint8(slots[cy]) | ci);
    208 			}
    209 			if (ci == 128) {
    210 				cy++;
    211 				ci = 1;
    212 			} else {
    213 				ci <<= 1;
    214 			}
    215 			capacity--;
    216 		}
    217 	}
    218 
    219 	function slotAvailable(uint256 _byte, uint8 _bitVal) internal view returns (bool) {
    220 		return (uint8(slots[_byte]) | uint8(sharedSlots[_byte])) & _bitVal == 0;
    221 	}
    222 
    223 	function getPos(uint256 bit) internal pure returns (uint256[2] memory) {
    224 		int256 c;
    225 		uint256[2] memory r;
    226 
    227 		c = int256(bit) / 8;
    228 
    229 		r[0] = uint256(c);
    230 		r[1] = bit % 8;
    231 		return r;
    232 	}
    233 
    234 	function raw(uint256 _count, uint256 _offset) public view returns (bytes memory) {
    235 		bytes memory r;
    236 		uint256[2] memory c;
    237 		uint256 l_offset;
    238 		uint256 l_count;
    239 
    240 		if (_count == 0) {
    241 			_count = capacity / 8;
    242 		}
    243 		require(_offset % 8 == 0, "ERR_BOUNDARY");
    244 
    245 		c = getPos(_offset);
    246 		l_offset = uint256(c[0]);
    247 
    248 		l_count = _count / 8;
    249 		if (uint8(c[1]) > 1) {
    250 			l_count += 1;
    251 		}
    252 		r = new bytes(l_count);
    253 		for (uint256 i = 0; i < l_count; i++) {
    254 			r[i] = slots[i + l_offset] | sharedSlots[i + l_offset];
    255 		}
    256 		return r;
    257 	}
    258 
    259 	// Implements Expire
    260 	function applyExpiry() public returns(uint8) {
    261 		if (expires == 0) {
    262 			return 0;
    263 		}
    264 		if (expired) {
    265 			return 1;
    266 		}
    267 		if (block.timestamp >= expires) {
    268 			expired = true;
    269 			emit Expired(block.timestamp);
    270 			return 2;
    271 		}
    272 		return 0;
    273 	}
    274 
    275 	// Implements EIP165
    276 	function supportsInterface(bytes4 _sum) public pure returns (bool) {
    277 		if (_sum == 0x0d7491f8) { // Seal
    278 			return true;
    279 		}
    280 		if (_sum == 0xabe1f1f5) { // Writer
    281 			return true;
    282 		}
    283 		if (_sum == 0x841a0e94) { // Expire
    284 			return true;
    285 		}
    286 		if (_sum == 0x01ffc9a7) { // ERC165
    287 			return true;
    288 		}
    289 		return false;
    290 	}
    291 }