dexorder
This commit is contained in:
679
lib_openzeppelin_contracts/certora/specs/ERC721.spec
Normal file
679
lib_openzeppelin_contracts/certora/specs/ERC721.spec
Normal file
@@ -0,0 +1,679 @@
|
||||
import "helpers/helpers.spec";
|
||||
import "methods/IERC721.spec";
|
||||
import "methods/IERC721Receiver.spec";
|
||||
|
||||
methods {
|
||||
// exposed for FV
|
||||
function mint(address,uint256) external;
|
||||
function safeMint(address,uint256) external;
|
||||
function safeMint(address,uint256,bytes) external;
|
||||
function burn(uint256) external;
|
||||
|
||||
function unsafeOwnerOf(uint256) external returns (address) envfree;
|
||||
function unsafeGetApproved(uint256) external returns (address) envfree;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Helpers │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
|
||||
definition authSanity(env e) returns bool = e.msg.sender != 0;
|
||||
|
||||
// Could be broken in theory, but not in practice
|
||||
definition balanceLimited(address account) returns bool = balanceOf(account) < max_uint256;
|
||||
|
||||
function helperTransferWithRevert(env e, method f, address from, address to, uint256 tokenId) {
|
||||
if (f.selector == sig:transferFrom(address,address,uint256).selector) {
|
||||
transferFrom@withrevert(e, from, to, tokenId);
|
||||
} else if (f.selector == sig:safeTransferFrom(address,address,uint256).selector) {
|
||||
safeTransferFrom@withrevert(e, from, to, tokenId);
|
||||
} else if (f.selector == sig:safeTransferFrom(address,address,uint256,bytes).selector) {
|
||||
bytes params;
|
||||
require params.length < 0xffff;
|
||||
safeTransferFrom@withrevert(e, from, to, tokenId, params);
|
||||
} else {
|
||||
calldataarg args;
|
||||
f@withrevert(e, args);
|
||||
}
|
||||
}
|
||||
|
||||
function helperMintWithRevert(env e, method f, address to, uint256 tokenId) {
|
||||
if (f.selector == sig:mint(address,uint256).selector) {
|
||||
mint@withrevert(e, to, tokenId);
|
||||
} else if (f.selector == sig:safeMint(address,uint256).selector) {
|
||||
safeMint@withrevert(e, to, tokenId);
|
||||
} else if (f.selector == sig:safeMint(address,uint256,bytes).selector) {
|
||||
bytes params;
|
||||
require params.length < 0xffff;
|
||||
safeMint@withrevert(e, to, tokenId, params);
|
||||
} else {
|
||||
require false;
|
||||
}
|
||||
}
|
||||
|
||||
function helperSoundFnCall(env e, method f) {
|
||||
if (f.selector == sig:mint(address,uint256).selector) {
|
||||
address to; uint256 tokenId;
|
||||
require balanceLimited(to);
|
||||
requireInvariant notMintedUnset(tokenId);
|
||||
mint(e, to, tokenId);
|
||||
} else if (f.selector == sig:safeMint(address,uint256).selector) {
|
||||
address to; uint256 tokenId;
|
||||
require balanceLimited(to);
|
||||
requireInvariant notMintedUnset(tokenId);
|
||||
safeMint(e, to, tokenId);
|
||||
} else if (f.selector == sig:safeMint(address,uint256,bytes).selector) {
|
||||
address to; uint256 tokenId; bytes data;
|
||||
require data.length < 0xffff;
|
||||
require balanceLimited(to);
|
||||
requireInvariant notMintedUnset(tokenId);
|
||||
safeMint(e, to, tokenId, data);
|
||||
} else if (f.selector == sig:burn(uint256).selector) {
|
||||
uint256 tokenId;
|
||||
requireInvariant ownerHasBalance(tokenId);
|
||||
requireInvariant notMintedUnset(tokenId);
|
||||
burn(e, tokenId);
|
||||
} else if (f.selector == sig:transferFrom(address,address,uint256).selector) {
|
||||
address from; address to; uint256 tokenId;
|
||||
require balanceLimited(to);
|
||||
requireInvariant ownerHasBalance(tokenId);
|
||||
requireInvariant notMintedUnset(tokenId);
|
||||
transferFrom(e, from, to, tokenId);
|
||||
} else if (f.selector == sig:safeTransferFrom(address,address,uint256).selector) {
|
||||
address from; address to; uint256 tokenId;
|
||||
require balanceLimited(to);
|
||||
requireInvariant ownerHasBalance(tokenId);
|
||||
requireInvariant notMintedUnset(tokenId);
|
||||
safeTransferFrom(e, from, to, tokenId);
|
||||
} else if (f.selector == sig:safeTransferFrom(address,address,uint256,bytes).selector) {
|
||||
address from; address to; uint256 tokenId; bytes data;
|
||||
require data.length < 0xffff;
|
||||
require balanceLimited(to);
|
||||
requireInvariant ownerHasBalance(tokenId);
|
||||
requireInvariant notMintedUnset(tokenId);
|
||||
safeTransferFrom(e, from, to, tokenId, data);
|
||||
} else {
|
||||
calldataarg args;
|
||||
f(e, args);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Ghost & hooks: ownership count │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
ghost mathint _ownedTotal {
|
||||
init_state axiom _ownedTotal == 0;
|
||||
}
|
||||
|
||||
ghost mapping(address => mathint) _ownedByUser {
|
||||
init_state axiom forall address a. _ownedByUser[a] == 0;
|
||||
}
|
||||
|
||||
hook Sstore _owners[KEY uint256 tokenId] address newOwner (address oldOwner) STORAGE {
|
||||
_ownedByUser[newOwner] = _ownedByUser[newOwner] + to_mathint(newOwner != 0 ? 1 : 0);
|
||||
_ownedByUser[oldOwner] = _ownedByUser[oldOwner] - to_mathint(oldOwner != 0 ? 1 : 0);
|
||||
_ownedTotal = _ownedTotal + to_mathint(newOwner != 0 ? 1 : 0) - to_mathint(oldOwner != 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Ghost & hooks: sum of all balances │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
ghost mathint _supply {
|
||||
init_state axiom _supply == 0;
|
||||
}
|
||||
|
||||
ghost mapping(address => mathint) _balances {
|
||||
init_state axiom forall address a. _balances[a] == 0;
|
||||
}
|
||||
|
||||
hook Sstore _balances[KEY address addr] uint256 newValue (uint256 oldValue) STORAGE {
|
||||
_supply = _supply - oldValue + newValue;
|
||||
}
|
||||
|
||||
// TODO: This used to not be necessary. We should try to remove it. In order to do so, we will probably need to add
|
||||
// many "preserved" directive that require the "balanceOfConsistency" invariant on the accounts involved.
|
||||
hook Sload uint256 value _balances[KEY address user] STORAGE {
|
||||
require _balances[user] == to_mathint(value);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: number of owned tokens is the sum of all balances │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant ownedTotalIsSumOfBalances()
|
||||
_ownedTotal == _supply
|
||||
{
|
||||
preserved mint(address to, uint256 tokenId) with (env e) {
|
||||
require balanceLimited(to);
|
||||
}
|
||||
preserved safeMint(address to, uint256 tokenId) with (env e) {
|
||||
require balanceLimited(to);
|
||||
}
|
||||
preserved safeMint(address to, uint256 tokenId, bytes data) with (env e) {
|
||||
require balanceLimited(to);
|
||||
}
|
||||
preserved burn(uint256 tokenId) with (env e) {
|
||||
requireInvariant ownerHasBalance(tokenId);
|
||||
requireInvariant balanceOfConsistency(ownerOf(tokenId));
|
||||
}
|
||||
preserved transferFrom(address from, address to, uint256 tokenId) with (env e) {
|
||||
require balanceLimited(to);
|
||||
requireInvariant ownerHasBalance(tokenId);
|
||||
requireInvariant balanceOfConsistency(from);
|
||||
requireInvariant balanceOfConsistency(to);
|
||||
}
|
||||
preserved safeTransferFrom(address from, address to, uint256 tokenId) with (env e) {
|
||||
require balanceLimited(to);
|
||||
requireInvariant ownerHasBalance(tokenId);
|
||||
requireInvariant balanceOfConsistency(from);
|
||||
requireInvariant balanceOfConsistency(to);
|
||||
}
|
||||
preserved safeTransferFrom(address from, address to, uint256 tokenId, bytes data) with (env e) {
|
||||
require balanceLimited(to);
|
||||
requireInvariant ownerHasBalance(tokenId);
|
||||
requireInvariant balanceOfConsistency(from);
|
||||
requireInvariant balanceOfConsistency(to);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: balanceOf is the number of tokens owned │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant balanceOfConsistency(address user)
|
||||
to_mathint(balanceOf(user)) == _ownedByUser[user] &&
|
||||
to_mathint(balanceOf(user)) == _balances[user]
|
||||
{
|
||||
preserved {
|
||||
require balanceLimited(user);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: owner of a token must have some balance │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant ownerHasBalance(uint256 tokenId)
|
||||
balanceOf(ownerOf(tokenId)) > 0
|
||||
{
|
||||
preserved {
|
||||
requireInvariant balanceOfConsistency(ownerOf(tokenId));
|
||||
require balanceLimited(ownerOf(tokenId));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: balance of address(0) is 0 │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule zeroAddressBalanceRevert() {
|
||||
balanceOf@withrevert(0);
|
||||
assert lastReverted;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: address(0) has no authorized operator │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant zeroAddressHasNoApprovedOperator(address a)
|
||||
!isApprovedForAll(0, a)
|
||||
{
|
||||
preserved with (env e) {
|
||||
require nonzerosender(e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: tokens that do not exist are not owned and not approved │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant notMintedUnset(uint256 tokenId)
|
||||
unsafeOwnerOf(tokenId) == 0 => unsafeGetApproved(tokenId) == 0;
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: unsafeOwnerOf and unsafeGetApproved don't revert + ownerOf and getApproved revert if token does not exist │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule notMintedRevert(uint256 tokenId) {
|
||||
requireInvariant notMintedUnset(tokenId);
|
||||
|
||||
address _owner = unsafeOwnerOf@withrevert(tokenId);
|
||||
assert !lastReverted;
|
||||
|
||||
address _approved = unsafeGetApproved@withrevert(tokenId);
|
||||
assert !lastReverted;
|
||||
|
||||
address owner = ownerOf@withrevert(tokenId);
|
||||
assert lastReverted <=> _owner == 0;
|
||||
assert !lastReverted => _owner == owner;
|
||||
|
||||
address approved = getApproved@withrevert(tokenId);
|
||||
assert lastReverted <=> _owner == 0;
|
||||
assert !lastReverted => _approved == approved;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rules: total supply can only change through mint and burn │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule supplyChange(env e) {
|
||||
require nonzerosender(e);
|
||||
requireInvariant zeroAddressHasNoApprovedOperator(e.msg.sender);
|
||||
|
||||
mathint supplyBefore = _supply;
|
||||
method f; helperSoundFnCall(e, f);
|
||||
mathint supplyAfter = _supply;
|
||||
|
||||
assert supplyAfter > supplyBefore => (
|
||||
supplyAfter == supplyBefore + 1 &&
|
||||
(
|
||||
f.selector == sig:mint(address,uint256).selector ||
|
||||
f.selector == sig:safeMint(address,uint256).selector ||
|
||||
f.selector == sig:safeMint(address,uint256,bytes).selector
|
||||
)
|
||||
);
|
||||
assert supplyAfter < supplyBefore => (
|
||||
supplyAfter == supplyBefore - 1 &&
|
||||
f.selector == sig:burn(uint256).selector
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rules: balanceOf can only change through mint, burn or transfers. balanceOf cannot change by more than 1. │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule balanceChange(env e, address account) {
|
||||
requireInvariant balanceOfConsistency(account);
|
||||
require balanceLimited(account);
|
||||
|
||||
mathint balanceBefore = balanceOf(account);
|
||||
method f; helperSoundFnCall(e, f);
|
||||
mathint balanceAfter = balanceOf(account);
|
||||
|
||||
// balance can change by at most 1
|
||||
assert balanceBefore != balanceAfter => (
|
||||
balanceAfter == balanceBefore - 1 ||
|
||||
balanceAfter == balanceBefore + 1
|
||||
);
|
||||
|
||||
// only selected function can change balances
|
||||
assert balanceBefore != balanceAfter => (
|
||||
f.selector == sig:transferFrom(address,address,uint256).selector ||
|
||||
f.selector == sig:safeTransferFrom(address,address,uint256).selector ||
|
||||
f.selector == sig:safeTransferFrom(address,address,uint256,bytes).selector ||
|
||||
f.selector == sig:mint(address,uint256).selector ||
|
||||
f.selector == sig:safeMint(address,uint256).selector ||
|
||||
f.selector == sig:safeMint(address,uint256,bytes).selector ||
|
||||
f.selector == sig:burn(uint256).selector
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rules: ownership can only change through mint, burn or transfers. │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule ownershipChange(env e, uint256 tokenId) {
|
||||
require nonzerosender(e);
|
||||
requireInvariant zeroAddressHasNoApprovedOperator(e.msg.sender);
|
||||
|
||||
address ownerBefore = unsafeOwnerOf(tokenId);
|
||||
method f; helperSoundFnCall(e, f);
|
||||
address ownerAfter = unsafeOwnerOf(tokenId);
|
||||
|
||||
assert ownerBefore == 0 && ownerAfter != 0 => (
|
||||
f.selector == sig:mint(address,uint256).selector ||
|
||||
f.selector == sig:safeMint(address,uint256).selector ||
|
||||
f.selector == sig:safeMint(address,uint256,bytes).selector
|
||||
);
|
||||
|
||||
assert ownerBefore != 0 && ownerAfter == 0 => (
|
||||
f.selector == sig:burn(uint256).selector
|
||||
);
|
||||
|
||||
assert (ownerBefore != ownerAfter && ownerBefore != 0 && ownerAfter != 0) => (
|
||||
f.selector == sig:transferFrom(address,address,uint256).selector ||
|
||||
f.selector == sig:safeTransferFrom(address,address,uint256).selector ||
|
||||
f.selector == sig:safeTransferFrom(address,address,uint256,bytes).selector
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rules: token approval can only change through approve or transfers (implicitly). │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule approvalChange(env e, uint256 tokenId) {
|
||||
address approvalBefore = unsafeGetApproved(tokenId);
|
||||
method f; helperSoundFnCall(e, f);
|
||||
address approvalAfter = unsafeGetApproved(tokenId);
|
||||
|
||||
// approve can set any value, other functions reset
|
||||
assert approvalBefore != approvalAfter => (
|
||||
f.selector == sig:approve(address,uint256).selector ||
|
||||
(
|
||||
(
|
||||
f.selector == sig:transferFrom(address,address,uint256).selector ||
|
||||
f.selector == sig:safeTransferFrom(address,address,uint256).selector ||
|
||||
f.selector == sig:safeTransferFrom(address,address,uint256,bytes).selector ||
|
||||
f.selector == sig:burn(uint256).selector
|
||||
) && approvalAfter == 0
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rules: approval for all tokens can only change through isApprovedForAll. │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule approvedForAllChange(env e, address owner, address spender) {
|
||||
bool approvedForAllBefore = isApprovedForAll(owner, spender);
|
||||
method f; helperSoundFnCall(e, f);
|
||||
bool approvedForAllAfter = isApprovedForAll(owner, spender);
|
||||
|
||||
assert approvedForAllBefore != approvedForAllAfter => f.selector == sig:setApprovalForAll(address,bool).selector;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: transferFrom behavior and side effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule transferFrom(env e, address from, address to, uint256 tokenId) {
|
||||
require nonpayable(e);
|
||||
require authSanity(e);
|
||||
|
||||
address operator = e.msg.sender;
|
||||
uint256 otherTokenId;
|
||||
address otherAccount;
|
||||
|
||||
requireInvariant ownerHasBalance(tokenId);
|
||||
require balanceLimited(to);
|
||||
|
||||
uint256 balanceOfFromBefore = balanceOf(from);
|
||||
uint256 balanceOfToBefore = balanceOf(to);
|
||||
uint256 balanceOfOtherBefore = balanceOf(otherAccount);
|
||||
address ownerBefore = unsafeOwnerOf(tokenId);
|
||||
address otherOwnerBefore = unsafeOwnerOf(otherTokenId);
|
||||
address approvalBefore = unsafeGetApproved(tokenId);
|
||||
address otherApprovalBefore = unsafeGetApproved(otherTokenId);
|
||||
|
||||
transferFrom@withrevert(e, from, to, tokenId);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> (
|
||||
from == ownerBefore &&
|
||||
from != 0 &&
|
||||
to != 0 &&
|
||||
(operator == from || operator == approvalBefore || isApprovedForAll(ownerBefore, operator))
|
||||
);
|
||||
|
||||
// effect
|
||||
assert success => (
|
||||
to_mathint(balanceOf(from)) == balanceOfFromBefore - assert_uint256(from != to ? 1 : 0) &&
|
||||
to_mathint(balanceOf(to)) == balanceOfToBefore + assert_uint256(from != to ? 1 : 0) &&
|
||||
unsafeOwnerOf(tokenId) == to &&
|
||||
unsafeGetApproved(tokenId) == 0
|
||||
);
|
||||
|
||||
// no side effect
|
||||
assert balanceOf(otherAccount) != balanceOfOtherBefore => (otherAccount == from || otherAccount == to);
|
||||
assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId;
|
||||
assert unsafeGetApproved(otherTokenId) != otherApprovalBefore => otherTokenId == tokenId;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: safeTransferFrom behavior and side effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule safeTransferFrom(env e, method f, address from, address to, uint256 tokenId) filtered { f ->
|
||||
f.selector == sig:safeTransferFrom(address,address,uint256).selector ||
|
||||
f.selector == sig:safeTransferFrom(address,address,uint256,bytes).selector
|
||||
} {
|
||||
require nonpayable(e);
|
||||
require authSanity(e);
|
||||
|
||||
address operator = e.msg.sender;
|
||||
uint256 otherTokenId;
|
||||
address otherAccount;
|
||||
|
||||
requireInvariant ownerHasBalance(tokenId);
|
||||
require balanceLimited(to);
|
||||
|
||||
uint256 balanceOfFromBefore = balanceOf(from);
|
||||
uint256 balanceOfToBefore = balanceOf(to);
|
||||
uint256 balanceOfOtherBefore = balanceOf(otherAccount);
|
||||
address ownerBefore = unsafeOwnerOf(tokenId);
|
||||
address otherOwnerBefore = unsafeOwnerOf(otherTokenId);
|
||||
address approvalBefore = unsafeGetApproved(tokenId);
|
||||
address otherApprovalBefore = unsafeGetApproved(otherTokenId);
|
||||
|
||||
helperTransferWithRevert(e, f, from, to, tokenId);
|
||||
bool success = !lastReverted;
|
||||
|
||||
assert success <=> (
|
||||
from == ownerBefore &&
|
||||
from != 0 &&
|
||||
to != 0 &&
|
||||
(operator == from || operator == approvalBefore || isApprovedForAll(ownerBefore, operator))
|
||||
);
|
||||
|
||||
// effect
|
||||
assert success => (
|
||||
to_mathint(balanceOf(from)) == balanceOfFromBefore - assert_uint256(from != to ? 1: 0) &&
|
||||
to_mathint(balanceOf(to)) == balanceOfToBefore + assert_uint256(from != to ? 1: 0) &&
|
||||
unsafeOwnerOf(tokenId) == to &&
|
||||
unsafeGetApproved(tokenId) == 0
|
||||
);
|
||||
|
||||
// no side effect
|
||||
assert balanceOf(otherAccount) != balanceOfOtherBefore => (otherAccount == from || otherAccount == to);
|
||||
assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId;
|
||||
assert unsafeGetApproved(otherTokenId) != otherApprovalBefore => otherTokenId == tokenId;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: mint behavior and side effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule mint(env e, address to, uint256 tokenId) {
|
||||
require nonpayable(e);
|
||||
requireInvariant notMintedUnset(tokenId);
|
||||
|
||||
uint256 otherTokenId;
|
||||
address otherAccount;
|
||||
|
||||
require balanceLimited(to);
|
||||
|
||||
mathint supplyBefore = _supply;
|
||||
uint256 balanceOfToBefore = balanceOf(to);
|
||||
uint256 balanceOfOtherBefore = balanceOf(otherAccount);
|
||||
address ownerBefore = unsafeOwnerOf(tokenId);
|
||||
address otherOwnerBefore = unsafeOwnerOf(otherTokenId);
|
||||
|
||||
mint@withrevert(e, to, tokenId);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> (
|
||||
ownerBefore == 0 &&
|
||||
to != 0
|
||||
);
|
||||
|
||||
// effect
|
||||
assert success => (
|
||||
_supply == supplyBefore + 1 &&
|
||||
to_mathint(balanceOf(to)) == balanceOfToBefore + 1 &&
|
||||
unsafeOwnerOf(tokenId) == to
|
||||
);
|
||||
|
||||
// no side effect
|
||||
assert balanceOf(otherAccount) != balanceOfOtherBefore => otherAccount == to;
|
||||
assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: safeMint behavior and side effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule safeMint(env e, method f, address to, uint256 tokenId) filtered { f ->
|
||||
f.selector == sig:safeMint(address,uint256).selector ||
|
||||
f.selector == sig:safeMint(address,uint256,bytes).selector
|
||||
} {
|
||||
require nonpayable(e);
|
||||
requireInvariant notMintedUnset(tokenId);
|
||||
|
||||
uint256 otherTokenId;
|
||||
address otherAccount;
|
||||
|
||||
require balanceLimited(to);
|
||||
|
||||
mathint supplyBefore = _supply;
|
||||
uint256 balanceOfToBefore = balanceOf(to);
|
||||
uint256 balanceOfOtherBefore = balanceOf(otherAccount);
|
||||
address ownerBefore = unsafeOwnerOf(tokenId);
|
||||
address otherOwnerBefore = unsafeOwnerOf(otherTokenId);
|
||||
|
||||
helperMintWithRevert(e, f, to, tokenId);
|
||||
bool success = !lastReverted;
|
||||
|
||||
assert success <=> (
|
||||
ownerBefore == 0 &&
|
||||
to != 0
|
||||
);
|
||||
|
||||
// effect
|
||||
assert success => (
|
||||
_supply == supplyBefore + 1 &&
|
||||
to_mathint(balanceOf(to)) == balanceOfToBefore + 1 &&
|
||||
unsafeOwnerOf(tokenId) == to
|
||||
);
|
||||
|
||||
// no side effect
|
||||
assert balanceOf(otherAccount) != balanceOfOtherBefore => otherAccount == to;
|
||||
assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: burn behavior and side effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule burn(env e, uint256 tokenId) {
|
||||
require nonpayable(e);
|
||||
|
||||
address from = unsafeOwnerOf(tokenId);
|
||||
uint256 otherTokenId;
|
||||
address otherAccount;
|
||||
|
||||
requireInvariant ownerHasBalance(tokenId);
|
||||
|
||||
mathint supplyBefore = _supply;
|
||||
uint256 balanceOfFromBefore = balanceOf(from);
|
||||
uint256 balanceOfOtherBefore = balanceOf(otherAccount);
|
||||
address ownerBefore = unsafeOwnerOf(tokenId);
|
||||
address otherOwnerBefore = unsafeOwnerOf(otherTokenId);
|
||||
address otherApprovalBefore = unsafeGetApproved(otherTokenId);
|
||||
|
||||
burn@withrevert(e, tokenId);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> (
|
||||
ownerBefore != 0
|
||||
);
|
||||
|
||||
// effect
|
||||
assert success => (
|
||||
_supply == supplyBefore - 1 &&
|
||||
to_mathint(balanceOf(from)) == balanceOfFromBefore - 1 &&
|
||||
unsafeOwnerOf(tokenId) == 0 &&
|
||||
unsafeGetApproved(tokenId) == 0
|
||||
);
|
||||
|
||||
// no side effect
|
||||
assert balanceOf(otherAccount) != balanceOfOtherBefore => otherAccount == from;
|
||||
assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId;
|
||||
assert unsafeGetApproved(otherTokenId) != otherApprovalBefore => otherTokenId == tokenId;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: approve behavior and side effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule approve(env e, address spender, uint256 tokenId) {
|
||||
require nonpayable(e);
|
||||
require authSanity(e);
|
||||
|
||||
address caller = e.msg.sender;
|
||||
address owner = unsafeOwnerOf(tokenId);
|
||||
uint256 otherTokenId;
|
||||
|
||||
address otherApprovalBefore = unsafeGetApproved(otherTokenId);
|
||||
|
||||
approve@withrevert(e, spender, tokenId);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> (
|
||||
owner != 0 &&
|
||||
(owner == caller || isApprovedForAll(owner, caller))
|
||||
);
|
||||
|
||||
// effect
|
||||
assert success => unsafeGetApproved(tokenId) == spender;
|
||||
|
||||
// no side effect
|
||||
assert unsafeGetApproved(otherTokenId) != otherApprovalBefore => otherTokenId == tokenId;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: setApprovalForAll behavior and side effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule setApprovalForAll(env e, address operator, bool approved) {
|
||||
require nonpayable(e);
|
||||
|
||||
address owner = e.msg.sender;
|
||||
address otherOwner;
|
||||
address otherOperator;
|
||||
|
||||
bool otherIsApprovedForAllBefore = isApprovedForAll(otherOwner, otherOperator);
|
||||
|
||||
setApprovalForAll@withrevert(e, operator, approved);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> operator != 0;
|
||||
|
||||
// effect
|
||||
assert success => isApprovedForAll(owner, operator) == approved;
|
||||
|
||||
// no side effect
|
||||
assert isApprovedForAll(otherOwner, otherOperator) != otherIsApprovedForAllBefore => (
|
||||
otherOwner == owner &&
|
||||
otherOperator == operator
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user