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 }