Transaction Hash:
Block:
17801779 at Jul-29-2023 10:49:11 PM +UTC
Transaction Fee:
0.002319816887960905 ETH
$4.55
Gas Used:
82,805 Gas / 28.015420421 Gwei
Emitted Events:
| 324 |
EthXYToken.Transfer( _from=0x0000000000000000000000000000000000000000, _to=[Sender] 0x3ac7084e8ce0757261b79f87a8e417d2df3d24d3, _value=100000000000000000000 )
|
| 325 |
Grid.PlotPurchase( buyer=[Sender] 0x3ac7084e8ce0757261b79f87a8e417d2df3d24d3, coord=288, price=40000000000000000, purchase_count=3 )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x055D0F4a...83c955e93 | 208.208891915270142865 Eth | 208.218891915270142865 Eth | 0.01 | ||
| 0x3Ac7084e...2dF3d24d3 |
97.833542587458251261 Eth
Nonce: 565
|
97.791222770570290356 Eth
Nonce: 566
| 0.042319816887960905 | ||
|
0x4675C7e5...ef3b0a263
Miner
| (Coinbase: MEV Builder) | 60.676487087632162415 Eth | 60.676694100132162415 Eth | 0.0002070125 | |
| 0xa666b864...3570857b2 | 4.62 Eth | 4.65 Eth | 0.03 | ||
| 0xB1e69773...5102F282D | |||||
| 0xCe96cc49...F188fd035 |
Execution Trace
ETH 0.04
Grid.buy_plot( coord=288 )
- ETH 0.03
0xa666b86470b3bb040715f155d08c3fd3570857b2.CALL( ) - ETH 0.01
TokenRedeemer.CALL( ) -
EthXYToken.mint( _to=0x3Ac7084e8ce0757261B79F87a8E417D2dF3d24d3, _value=100000000000000000000 )
File 1 of 3: Grid
File 2 of 3: EthXYToken
File 3 of 3: TokenRedeemer
# @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