ETH Price: $2,271.05 (-7.14%)

Transaction Decoder

Block:
17802061 at Jul-29-2023 11:46:11 PM +UTC
Transaction Fee:
0.00165910945742944 ETH $3.77
Gas Used:
82,730 Gas / 20.054508128 Gwei

Emitted Events:

161 EthXYToken.Transfer( _from=0x0000000000000000000000000000000000000000, _to=[Sender] 0x3a43f88bb667dad028bae4b25cf84d6c6468b87e, _value=100000000000000000000 )
162 Grid.PlotPurchase( buyer=[Sender] 0x3a43f88bb667dad028bae4b25cf84d6c6468b87e, coord=9212, price=80000000000000000, purchase_count=4 )

Account State Difference:

  Address   Before After State Difference Code
0x055D0F4a...83c955e93 494.649394876772481394 Eth494.669394876772481394 Eth0.02
(Lido: Execution Layer Rewards Vault)
121.567657184999493059 Eth121.567671249099493059 Eth0.0000140641
0x3a43F88b...c6468b87e
4.87606722166787198 Eth
Nonce: 205
4.79440811221044254 Eth
Nonce: 206
0.08165910945742944
0x91E6bb62...6701135FD 1.8925830591308932 Eth1.9525830591308932 Eth0.06
0xB1e69773...5102F282D
0xCe96cc49...F188fd035

Execution Trace

ETH 0.08 Grid.buy_plot( coord=9212 )
  • ETH 0.06 0x91e6bb626d2cb59f0f2fb1e03a8bfe76701135fd.CALL( )
  • ETH 0.02 TokenRedeemer.CALL( )
  • EthXYToken.mint( _to=0x3a43F88bb667dAd028Bae4b25Cf84D6c6468b87e, _value=100000000000000000000 )
    File 1 of 3: Grid
    # @version ^0.3.9
    # @title Grid
    
    from vyper.interfaces import ERC20Detailed
    
    struct Plot:
        owner: address
        purchase_count: uint256
    
    grid: public(HashMap[uint256, Plot])
    
    BASE_PRICE: constant(uint256) = 10 ** 16
    
    plots_owned: public(HashMap[address, uint256])
    
    treasury: public(address)
    
    token: public(immutable(address))
    mint_per_plot: immutable(uint256)
    
    interface MintableToken:
        def mint(to: address, amount: uint256): nonpayable
    
    event PlotPurchase:
        buyer: indexed(address)
        coord: indexed(uint256)
        price: uint256
        purchase_count: uint256
    
    @external
    def __init__(treasury: address, _token: address):
        self.treasury = treasury
        token = _token
        mint_per_plot = 100 * 10 ** convert(ERC20Detailed(token).decimals(), uint256)
    
    @payable
    @external
    def buy_plot(coord: uint256):
        assert coord < 10000, "Invalid coord"
    
        # plot price increases by 2x each time it is purchased
        plot_price: uint256 = BASE_PRICE * (2 ** self.grid[coord].purchase_count)
        current_owner: address = self.grid[coord].owner
    
        assert msg.value >= plot_price, "Not enough ETH sent to buy plot"
        if msg.value > plot_price:
            send(msg.sender, msg.value - plot_price)
    
        # protocol takes 25% of the purchase price
        # 75% goes to the previous owner
        # if there is no previous owner, 100% goes to the protocol
        # the protocol is the owner of all plots at the start
        if current_owner != ZERO_ADDRESS:
            send(current_owner, plot_price * 3 / 4)
            if current_owner != msg.sender:
                self.plots_owned[current_owner] -= 1
                self.plots_owned[msg.sender] += 1
                self.grid[coord].owner = msg.sender
        else:
            self.grid[coord].owner = msg.sender
            self.plots_owned[msg.sender] += 1
    
        send(self.treasury, self.balance)
    
        self.grid[coord].purchase_count += 1
    
        # mint tokens for the buyer
        MintableToken(token).mint(msg.sender, mint_per_plot)
    
        log PlotPurchase(msg.sender, coord, plot_price, self.grid[coord].purchase_count)
    
    @view
    @external
    def get_all_owners(start_index: uint256 = 0, end_index: uint256 = 10000) -> DynArray[address, 10000]:
        owners: DynArray[address, 10000] = []
        for coord in range(start_index, start_index + 10000):
            if coord >= end_index:
                break
            owners.append(self.grid[coord].owner)
        return owners
    
    @view
    @external
    def get_all_purchase_counts(start_index: uint256 = 0, end_index: uint256 = 10000) -> DynArray[uint256, 10000]:
        purchase_counts: DynArray[uint256, 10000] = []
        for coord in range(start_index, start_index + 10000):
            if coord >= end_index:
                break
            purchase_counts.append(self.grid[coord].purchase_count)
        return purchase_counts
    
    @view
    @external
    def price(coord: uint256) -> uint256:
        return BASE_PRICE * (2 ** self.grid[coord].purchase_count)

    File 2 of 3: EthXYToken
    # @version ^0.3.9
    # @title EthXYToken
    
    from vyper.interfaces import ERC20
    
    implements: ERC20
    
    event Transfer:
        _from: indexed(address)
        _to: indexed(address)
        _value: uint256
    
    event Approval:
        _owner: indexed(address)
        _spender: indexed(address)
        _value: uint256
    
    name: public(immutable(String[10]))
    symbol: public(immutable(String[3]))
    decimals: public(constant(uint256)) = 18
    totalSupply: public(uint256)
    
    balanceOf: public(HashMap[address, uint256])
    allowance: public(HashMap[address, HashMap[address, uint256]])
    
    minter: public(address)
    burner: public(address)
    
    @external
    def __init__():
        name = "EthXYToken"
        symbol = "EXY"
        self.minter = msg.sender
        self.burner = msg.sender
    
    @external
    def set_minter(minter: address):
        assert msg.sender == self.minter
        self.minter = minter
    
    @external
    def set_burner(burner: address):
        assert msg.sender == self.burner
        self.burner = burner
    
    @external
    def approve(spender: address, amount: uint256) -> bool:
        self.allowance[msg.sender][spender] = amount
        log Approval(msg.sender, spender, amount)
        return True
    
    @external
    def increaseAllowance(spender: address, addedValue: uint256) -> bool:
        self.allowance[msg.sender][spender] += addedValue
        log Approval(msg.sender, spender, self.allowance[msg.sender][spender])
        return True
    
    @external
    def decreaseAllowance(spender: address, subtractedValue: uint256) -> bool:
        self.allowance[msg.sender][spender] -= subtractedValue
        log Approval(msg.sender, spender, self.allowance[msg.sender][spender])
        return True
    
    @external
    def transfer(_to: address, _value: uint256) -> bool:
        self.balanceOf[msg.sender] -= _value
        self.balanceOf[_to] += _value
        log Transfer(msg.sender, _to, _value)
        return True
    
    @external
    def transferFrom(_from: address, _to: address, _value: uint256) -> bool:
        self.allowance[_from][msg.sender] -= _value
        self.balanceOf[_from] -= _value
        self.balanceOf[_to] += _value
        log Transfer(_from, _to, _value)
        return True
    
    @external
    def mint(_to: address, _value: uint256):
        assert msg.sender == self.minter
        self.balanceOf[_to] += _value
        self.totalSupply += _value
        log Transfer(ZERO_ADDRESS, _to, _value)
    
    @external
    def burn(_value: uint256):
        assert msg.sender == self.burner
        self.balanceOf[msg.sender] -= _value
        self.totalSupply -= _value
        log Transfer(msg.sender, ZERO_ADDRESS, _value)
    
    ################################################################
    #                           EIP-2612                           #
    ################################################################
    
    nonces: public(HashMap[address, uint256])
    
    _DOMAIN_TYPEHASH: constant(bytes32) = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
    _PERMIT_TYPE_HASH: constant(bytes32) = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
    
    
    @external
    def permit(owner: address, spender: address, amount: uint256, deadline: uint256, v: uint8, r: bytes32, s: bytes32):
        assert deadline >= block.timestamp
        nonce: uint256 = self.nonces[owner]
        self.nonces[owner] = nonce + 1
    
        domain_separator: bytes32 = keccak256(
            _abi_encode(_DOMAIN_TYPEHASH, name, "1.0", chain.id, self)
        )
    
        struct_hash: bytes32 = keccak256(_abi_encode(_PERMIT_TYPE_HASH, owner, spender, amount, nonce, deadline))
        hash: bytes32 = keccak256(
            concat(
                b"\x19\x01",
                domain_separator,
                struct_hash
            )
        )
    
        assert owner == ecrecover(hash, v, r, s)
        self.nonces[owner] += 1
        self.allowance[owner][spender] = amount
        log Approval(owner, spender, amount)
    
    @internal
    def _mint(_to: address, _value: uint256):
        self.balanceOf[_to] += _value
        self.totalSupply += _value
        log Transfer(ZERO_ADDRESS, _to, _value)
    
    @internal
    def _burn(_from: address, _value: uint256):
        assert self.balanceOf[_from] >= _value
        self.balanceOf[_from] -= _value
        self.totalSupply -= _value
        log Transfer(_from, ZERO_ADDRESS, _value)

    File 3 of 3: TokenRedeemer
    # @version ^0.3.9
    # @title TokenRedeemer
    
    """
    Token redemption contract where users can redeem their tokens for ETH.
    They get (accumulated ETH) / (total supply) * (token balance)
    """
    
    from vyper.interfaces import ERC20
    
    token: public(ERC20)
    
    interface MintableBurnableToken:
        def mint(amount: uint256): nonpayable
        def burn(amount: uint256): nonpayable
    
    event Redeemed:
        redeemer: indexed(address)
        amount_earned: uint256
        amount_burned: uint256
    
    @external
    def __init__(_token: ERC20):
        self.token = _token
    
    event Attempt:
        user: indexed(address)
        amount: uint256
        allowance: uint256
        balance: uint256
    
    @external
    def redeem(amount: uint256 = 0):
        """
        Redeem tokens for ETH.
        """
        totalSupply: uint256 = self.token.totalSupply()
    
        amount_to_burn: uint256 = amount
        if amount_to_burn == 0:
            amount_to_burn = self.token.balanceOf(msg.sender)
    
        amount_earned: uint256 = (self.balance * amount_to_burn) / totalSupply
    
        assert self.token.allowance(msg.sender, self) >= amount_to_burn, "Not enough allowance"
        assert self.token.balanceOf(msg.sender) >= amount_to_burn, "Not enough balance"
    
        log Attempt(msg.sender, amount_to_burn, self.token.allowance(msg.sender, self), self.token.balanceOf(msg.sender))
    
        self.token.transferFrom(msg.sender, self, amount_to_burn)
    
        send(msg.sender, amount_earned)
        MintableBurnableToken(self.token.address).burn(amount_to_burn)
    
        log Redeemed(msg.sender, amount_earned, amount_to_burn)
    
    @view
    @external
    def claimable_amount(user: address) -> uint256:
        """
        Returns the amount of ETH that can be claimed.
        """
        totalSupply: uint256 = self.token.totalSupply()
        return (self.balance * self.token.balanceOf(user)) / totalSupply
    
    @payable
    @external
    def __default__():
        """
        Fallback function to receive ETH.
        """
        pass