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.

Revisions

  1. manolingam created this gist Oct 25, 2022.
    225 changes: 225 additions & 0 deletions GrazingAndSwapping.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,225 @@
    // 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;
    }
    }