diff --git a/solidity/src/MaybeMintERC721.sol b/solidity/src/MaybeMintERC721.sol new file mode 100644 index 0000000..0906222 --- /dev/null +++ b/solidity/src/MaybeMintERC721.sol @@ -0,0 +1,45 @@ +pragma solidity 0.8.24; + +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +/** + * @title MaybeMintERC721 + * @dev Mint ERC721 tokens for a fee in ERC20 tokens. As this simple token is intended for + * demonstration of batched EVM calls on Flow EVM using a single Cadence transaction, the + * minting process is random and has some chance of failure. + */ +contract MaybeMintERC721 is ERC721, Ownable { + IERC20 public denomination; + uint256 public mintCost; + address public beneficiary; + uint256 public totalSupply; + + constructor(string memory _name, string memory _symbol, address _erc20, uint256 _mintCost, address _beneficiary) + ERC721(_name, _symbol) + Ownable(msg.sender) + { + denomination = IERC20(_erc20); + mintCost = _mintCost; + beneficiary = _beneficiary; + totalSupply = 0; + } + + /** + * @dev Mint a new ERC721 token to the caller with some chance of failure. This is for + * demonstration purposes, intended to showcase how a single Cadence transaction can batch + * multiple EVM calls and condition final execution based on the result of any individual + * EVM call. + * + * NOTE: This contract address must be approved to transfer mintCost amount from the caller + * to the beneficiary before minting the ERC721 to pay for mint + */ + function mint() external { + // TODO: Get a random number to determine if the mint is successful + // TODO: Set token URI + totalSupply++; // increment the total supply + denomination.transferFrom(msg.sender, beneficiary, mintCost); // take payment for mint + _mint(msg.sender, totalSupply); // mint the token, assigning the next tokenId + } +} diff --git a/solidity/src/test/ExampleERC20.sol b/solidity/src/test/ExampleERC20.sol new file mode 100644 index 0000000..78fe9cc --- /dev/null +++ b/solidity/src/test/ExampleERC20.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; + +contract ExampleERC20 is ERC20, ERC20Burnable, Ownable, ERC20Permit { + constructor() ERC20("NAME", "SYMBOL") Ownable(msg.sender) ERC20Permit("NAME") {} + + function mint(address to, uint256 amount) public onlyOwner { + _mint(to, amount); + } +} diff --git a/solidity/test/MaybeMintERC721.t.sol b/solidity/test/MaybeMintERC721.t.sol new file mode 100644 index 0000000..b1b2f9d --- /dev/null +++ b/solidity/test/MaybeMintERC721.t.sol @@ -0,0 +1,42 @@ +pragma solidity 0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {MaybeMintERC721} from "../src/MaybeMintERC721.sol"; +import {ExampleERC20} from "../src/test/ExampleERC20.sol"; + +contract MaybeMintERC721Test is Test { + // Contracts + MaybeMintERC721 private erc721; + ExampleERC20 private erc20; + + // MaybeMintERC721 parameters + address payable beneficiary = payable(address(100)); + uint256 internal mintCost = 1 ether; + string internal name = "Maybe Mint ERC721 Test"; + string internal symbol = "MAYBE"; + + // Test values + address payable internal user = payable(address(101)); + + function setUp() public virtual { + vm.deal(user, 10 ether); + + erc20 = new ExampleERC20(); + erc721 = new MaybeMintERC721(name, symbol, address(erc20), mintCost, beneficiary); + } + + function test_mint() public { + erc20.mint(user, mintCost); // mint ERC20 to user + + vm.prank(user); + erc20.approve(address(erc721), mintCost); // approve the ERC20 to be spent by MaybeMintERC721 + + vm.prank(user); + erc721.mint(); // mint ERC721 to user + + assertEq(erc721.ownerOf(1), user); // user should own the ERC721 token + } +}