Skip to content

Commit

Permalink
Use onchain VRF to determine minting success (#11)
Browse files Browse the repository at this point in the history
* forge install: random-coin-toss

* remove submodule

* forge install: random-coin-toss

* update foundry.toml

* update foundry remappings

* add CadenceArchUtils library & test coverage on random mint

* update dependencies

* replace local dep with foundry installed dep

* remove onflow/random-coin-toss

* forge install: flow-sol-utils

* update solidity dependencies to use flow-sol-utils
  • Loading branch information
sisyphusSmiling authored Nov 7, 2024
1 parent 07c2e0c commit 74422d2
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 4 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
[submodule "solidity/lib/forge-std"]
path = solidity/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "solidity/lib/flow-sol-utils"]
path = solidity/lib/flow-sol-utils
url = https://github.com/onflow/flow-sol-utils
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ out = "./solidity/out"
libs = ["./solidity/lib"]
script = "./solidity/script"
test = "./solidity/test"
cache_path = "./solidity/cache"

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ ds-test/=solidity/lib/forge-std/lib/ds-test/src/
erc4626-tests/=solidity/lib/openzeppelin-contracts/lib/erc4626-tests/
forge-std/=solidity/lib/forge-std/src/
openzeppelin-contracts/=solidity/lib/openzeppelin-contracts/
flow-sol-utils=solidity/lib/flow-sol-utils/src/
1 change: 1 addition & 0 deletions solidity/lib/flow-sol-utils
Submodule flow-sol-utils added at 082dd3
2 changes: 1 addition & 1 deletion solidity/lib/openzeppelin-contracts
24 changes: 22 additions & 2 deletions solidity/src/MaybeMintERC721.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ 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";
import {CadenceArchUtils} from "flow-sol-utils/cadence-arch/CadenceArchUtils.sol";

/**
* @title MaybeMintERC721
Expand Down Expand Up @@ -36,10 +37,29 @@ contract MaybeMintERC721 is ERC721, Ownable {
* 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
// Randomly fail mint with 50% chance of reverting
_maybeMint();
}

/**
* @dev Mint a new ERC721 token to the caller with some chance of failure.
* NOTE: Production systems using random minting should leverage a commit-reveal scheme
* to ensure that the random minting transaction cannot be reverted on random result.
*/
function _maybeMint() internal {
_splitChanceRevert(); // randomly revert with 50% chance

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
// TODO: Set token URI
}

/**
* @dev Randomly revert with 50% chance
*/
function _splitChanceRevert() internal view {
uint64 random = CadenceArchUtils._revertibleRandom();
require(random % 2 == 0, "No mint for you!");
}
}
31 changes: 30 additions & 1 deletion solidity/test/MaybeMintERC721.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import {MaybeMintERC721} from "../src/MaybeMintERC721.sol";
import {ExampleERC20} from "../src/test/ExampleERC20.sol";

contract MaybeMintERC721Test is Test {
// Cadence Arch pre-compile address used to get onchain revertible randomness
address private cadenceArch = 0x0000000000000000000000010000000000000001;

// Contracts
MaybeMintERC721 private erc721;
ExampleERC20 private erc20;
Expand All @@ -28,15 +31,41 @@ contract MaybeMintERC721Test is Test {
erc721 = new MaybeMintERC721(name, symbol, address(erc20), mintCost, beneficiary);
}

function test_mint() public {
function testMintRandomEvenSucceeds() 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

// Mock the Cadence Arch precompile for revertibleRandom() call, returning 0 - mint should succeed
vm.mockCall(cadenceArch, abi.encodeWithSignature("revertibleRandom()"), abi.encode(uint64(0)));

vm.prank(user);
erc721.mint(); // mint ERC721 to user

assertEq(erc721.ownerOf(1), user); // user should own the ERC721 token
}

function testMintRandomOddFails() 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

// Mock the Cadence Arch precompile for revertibleRandom() call, returning 1 - mint should fail
vm.mockCall(cadenceArch, abi.encodeWithSignature("revertibleRandom()"), abi.encode(uint64(3)));

vm.prank(user);
vm.expectRevert("No mint for you!");
erc721.mint(); // Attempt to mint ERC721 to user - should revert
}

function testMintRandomWithoutApproveFails() public {
// Mock the Cadence Arch precompile for revertibleRandom() call, returning 0 - allows mint
vm.mockCall(cadenceArch, abi.encodeWithSignature("revertibleRandom()"), abi.encode(uint64(0)));

vm.prank(user);
vm.expectRevert();
erc721.mint(); // Attempt to mint ERC721 to user - reverts as user has not approved ERC20
}
}

0 comments on commit 74422d2

Please sign in to comment.