Skip to content

Instantly share code, notes, and snippets.

@manolingam
Created October 25, 2022 05:40
Show Gist options
  • Save manolingam/d62b6e62579a2e3e1cf4407eb634e98d to your computer and use it in GitHub Desktop.
Save manolingam/d62b6e62579a2e3e1cf4407eb634e98d to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
error NotYourBufficorn();
error BufficornOnSwap();
error BufficornNotGrazing();
error BufficornGrazing();
error InvalidSwapId();
error SwapAlreadyComplete();
error SwapExpired();
error NotOwner();
error DuplicateRecord();
error Paused();
contract GrazingAndSwapping {
uint public SWAP_PERIOD;
uint public MIN_GRAZING_PERIOD;
address public BUFFICORN_NFT_ADDRESS;
bool public IS_PAUSED;
address public OWNER;
// GRAZING INFORMATION STRUCT
struct GrazingInfo {
address grazer;
uint bufficornId;
bool isGrazing;
uint grazingStartTime;
uint grazingEndTime;
}
// MAPPING OF BUFFICORN IDS TO GRAZING INFO STRUCT
mapping (uint => GrazingInfo) public grazingInfo;
// SWAP STATUS ENUM
enum SwapStatus {Created, Completed}
// SWAP INFORMATION STRUCT
struct SwapInfo {
address swapInitiator;
uint initiatorBufficornId;
address swapExecutor;
uint executorBufficornId;
uint swapInitiatedTimestamp;
uint swapExecutedTimestamp;
string[] traitsSelectedForSwap;
SwapStatus swapStatus;
}
// COUNTING NUMBER OF SWAPS FOR SWAP ID
uint public numberOfSwapsCreated;
// MAPPING OF SWAP IDS TO ENCODED SWAP STRUCT
mapping (uint => bytes) internal swapHash;
// RECORDING THE LATEST SWAP PARTICIPATION TIME OF A BUFFICORN
mapping (uint => uint) public bufficornLastestSwapParticipationTime;
// EVENTS
event newSwap(address initiator, uint initiatorBufficornId, uint executorBufficornId, uint swapInitiatedTimestamp, string[] traitsSelectedForSwap, SwapStatus);
event finishSwap(address executor, uint initiatorBufficornId, uint executorBufficornId, uint swapExecutedTimestamp, SwapStatus);
event goneGrazing(address grazer, uint grazingBufficorn, uint grazingStartTime);
event goneHome(address grazer, uint grazingBufficorn, uint grazingEndTime);
// CONSTRUCTOR AND MODIFIERS ---------------------------------------------------------
constructor(uint _swapPeriod, uint _minGrazingPeriod, address _bufficornNftAddress) {
SWAP_PERIOD = _swapPeriod;
MIN_GRAZING_PERIOD = _minGrazingPeriod;
BUFFICORN_NFT_ADDRESS = _bufficornNftAddress;
OWNER = msg.sender;
IS_PAUSED = false;
}
modifier onlyOwner() {
if(msg.sender != OWNER) revert NotOwner();
_;
}
modifier notPaused() {
if(IS_PAUSED == true) revert Paused();
_;
}
// PUBLIC FUNTIONS ---------------------------------------------------------
function initiateTraitSwap(uint _initiatorBufficornId, uint _executorBufficornId, string[] calldata _traitsToSwap) public notPaused returns (uint) {
// revert if the caller is not the owner of the bufficorn he initiates the swap with
if (IERC721(BUFFICORN_NFT_ADDRESS).ownerOf(_initiatorBufficornId) != msg.sender) revert NotYourBufficorn();
// revert if the initiating bufficorn is on an another swap and the swap period has not past the current time
if(grazingInfo[_initiatorBufficornId].isGrazing == true) {
if (bufficornLastestSwapParticipationTime[_initiatorBufficornId] + SWAP_PERIOD < block.timestamp) revert BufficornOnSwap();
}
// revert if the executing bufficorn is on an another swap and the swap period has not past the current time
if(grazingInfo[_executorBufficornId].isGrazing == true) {
if (bufficornLastestSwapParticipationTime[_executorBufficornId] + SWAP_PERIOD < block.timestamp) revert BufficornOnSwap();
}
// if the initiating bufficorn is not already grazing, put it to grazing
if(grazingInfo[_initiatorBufficornId].isGrazing == false) {
_goGrazin(_initiatorBufficornId);
}
// locking the swap initiated time to use in other variable assignments
uint _swapInitiatedTimestamp = block.timestamp;
// creating the swap information
SwapInfo memory _swapInfo = SwapInfo(msg.sender, _initiatorBufficornId, address(0), _executorBufficornId,
_swapInitiatedTimestamp, 0, _traitsToSwap, SwapStatus.Created);
// increment the number of swaps created
numberOfSwapsCreated = numberOfSwapsCreated + 1;
// using the swap id from previous and storing the swap info encoded as hash
swapHash[numberOfSwapsCreated] = abi.encode(_swapInfo);
// updating the lastest swap participation time of the initiated bufficorn so that it can't be used on another swap
bufficornLastestSwapParticipationTime[_initiatorBufficornId] = _swapInitiatedTimestamp;
emit newSwap(msg.sender, _initiatorBufficornId, _executorBufficornId, _swapInitiatedTimestamp, _traitsToSwap, SwapStatus.Created);
return numberOfSwapsCreated;
}
function executeTraitSwap(uint _swapId) public notPaused {
// revert if swap id not found
if(_swapId > numberOfSwapsCreated) revert InvalidSwapId();
// decoding the swap info
SwapInfo memory _swapInfo = abi.decode(swapHash[_swapId], (SwapInfo));
// revert if the caller is not the owner of the executing bufficorn
if (IERC721(BUFFICORN_NFT_ADDRESS).ownerOf(_swapInfo.executorBufficornId) != msg.sender) revert NotYourBufficorn();
// revert if swap already complete or the time has expired
if(_swapInfo.swapStatus == SwapStatus.Completed) revert SwapAlreadyComplete();
if(_swapInfo.swapInitiatedTimestamp + SWAP_PERIOD < block.timestamp) revert SwapExpired();
// if the executor bufficorn not under grazing, let it graze
if(grazingInfo[_swapInfo.executorBufficornId].isGrazing == false) {
_goGrazin(_swapInfo.executorBufficornId);
}
// updating the swap info and encoding the updated information
_swapInfo.swapExecutor = msg.sender;
_swapInfo.swapExecutedTimestamp = block.timestamp;
_swapInfo.swapStatus = SwapStatus.Completed;
swapHash[_swapId] = abi.encode(_swapInfo);
emit finishSwap(msg.sender, _swapInfo.initiatorBufficornId, _swapInfo.executorBufficornId, _swapInfo.swapExecutedTimestamp, SwapStatus.Completed);
}
function withdrawBufficornFromGrazing(uint _bufficornId) public {
// revert if caller is not the bufficorn grazer
if (grazingInfo[_bufficornId].grazer != msg.sender) revert NotYourBufficorn();
// revert if bufficorn not already grazing
if (grazingInfo[_bufficornId].isGrazing == false) revert BufficornNotGrazing();
// revert if minimum grazing time has not past
if(grazingInfo[_bufficornId].grazingStartTime + MIN_GRAZING_PERIOD > block.timestamp) revert BufficornGrazing();
// revert if the bufficorn is currently on a swap period
if(bufficornLastestSwapParticipationTime[_bufficornId] + SWAP_PERIOD > block.timestamp) revert BufficornOnSwap();
// claim back bufficorn to wallet
_backToRanch(_bufficornId);
}
// INTERNAL FUNCTIONS ---------------------------------------------------------
function _goGrazin(uint _tokenId) internal {
IERC721(BUFFICORN_NFT_ADDRESS).approve(address(this), _tokenId);
IERC721(BUFFICORN_NFT_ADDRESS).safeTransferFrom(msg.sender, address(this), _tokenId);
uint _grazingStartTime = block.timestamp;
grazingInfo[_tokenId] = GrazingInfo(msg.sender, _tokenId, true, _grazingStartTime, 0);
emit goneGrazing(msg.sender, _tokenId, _grazingStartTime);
}
function _backToRanch(uint _tokenId) internal {
IERC721(BUFFICORN_NFT_ADDRESS).approve(address(this), _tokenId);
IERC721(BUFFICORN_NFT_ADDRESS).safeTransferFrom(address(this), msg.sender, _tokenId);
uint _grazingEndTime = block.timestamp;
grazingInfo[_tokenId].isGrazing = false;
grazingInfo[_tokenId].grazingEndTime = _grazingEndTime;
emit goneHome(msg.sender, _tokenId, _grazingEndTime);
}
// VIEW FUNCTION ---------------------------------------------------------
function decodeSwap(uint _swapId) public view returns(SwapInfo memory) {
if(_swapId > numberOfSwapsCreated) revert InvalidSwapId();
return abi.decode(swapHash[_swapId], (SwapInfo));
}
// ADMIN FUNCTIONS ---------------------------------------------------------
function updateSwapPeriod(uint _swapPeriod) public onlyOwner {
if(SWAP_PERIOD == _swapPeriod) revert DuplicateRecord();
SWAP_PERIOD = _swapPeriod;
}
function updateMinGrazingPeriod(uint _minGrazingPeriod) public onlyOwner {
if(MIN_GRAZING_PERIOD == _minGrazingPeriod) revert DuplicateRecord();
MIN_GRAZING_PERIOD = _minGrazingPeriod;
}
function toggleContractState() public onlyOwner {
IS_PAUSED = !IS_PAUSED;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment