|
@@ -9,6 +9,7 @@ methods {
|
|
|
function safeMint(address,uint256,bytes) external;
|
|
|
function burn(uint256) external;
|
|
|
|
|
|
+ function unsafeBalanceOf(address) external returns (uint256) envfree;
|
|
|
function unsafeOwnerOf(uint256) external returns (address) envfree;
|
|
|
function unsafeGetApproved(uint256) external returns (address) envfree;
|
|
|
}
|
|
@@ -19,12 +20,10 @@ methods {
|
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
|
*/
|
|
|
|
|
|
-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;
|
|
|
+definition balanceLimited(address account) returns bool = unsafeBalanceOf(account) < max_uint256;
|
|
|
|
|
|
-function helperTransferWithRevert(env e, method f, address from, address to, uint256 tokenId) {
|
|
|
+function helperTransferWithRevert(env e, method f, address from, address to, uint256 tokenId) returns bool {
|
|
|
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) {
|
|
@@ -37,9 +36,10 @@ function helperTransferWithRevert(env e, method f, address from, address to, uin
|
|
|
calldataarg args;
|
|
|
f@withrevert(e, args);
|
|
|
}
|
|
|
+ return !lastReverted;
|
|
|
}
|
|
|
|
|
|
-function helperMintWithRevert(env e, method f, address to, uint256 tokenId) {
|
|
|
+function helperMintWithRevert(env e, method f, address to, uint256 tokenId) returns bool {
|
|
|
if (f.selector == sig:mint(address,uint256).selector) {
|
|
|
mint@withrevert(e, to, tokenId);
|
|
|
} else if (f.selector == sig:safeMint(address,uint256).selector) {
|
|
@@ -51,6 +51,7 @@ function helperMintWithRevert(env e, method f, address to, uint256 tokenId) {
|
|
|
} else {
|
|
|
require false;
|
|
|
}
|
|
|
+ return !lastReverted;
|
|
|
}
|
|
|
|
|
|
function helperSoundFnCall(env e, method f) {
|
|
@@ -100,6 +101,17 @@ function helperSoundFnCall(env e, method f) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/*
|
|
|
+┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
|
+│ Helper: check that unsafe getter match their unsafe counterpart │
|
|
|
+└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
|
+*/
|
|
|
+rule unsafeConsistency(address user, uint256 tokenId) {
|
|
|
+ assert user != 0 => unsafeBalanceOf(user) == balanceOf(user);
|
|
|
+ assert unsafeOwnerOf(tokenId) != 0 => unsafeOwnerOf(tokenId) == ownerOf(tokenId);
|
|
|
+ assert unsafeOwnerOf(tokenId) != 0 => unsafeGetApproved(tokenId) == getApproved(tokenId);
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
|
│ Ghost & hooks: ownership count │
|
|
@@ -113,7 +125,7 @@ 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 {
|
|
|
+hook Sstore _owners[KEY uint256 tokenId] address newOwner (address oldOwner) {
|
|
|
_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);
|
|
@@ -132,13 +144,13 @@ 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 {
|
|
|
+hook Sstore _balances[KEY address addr] uint256 newValue (uint256 oldValue) {
|
|
|
_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 {
|
|
|
+hook Sload uint256 value _balances[KEY address user] {
|
|
|
require _balances[user] == to_mathint(value);
|
|
|
}
|
|
|
|
|
@@ -189,8 +201,8 @@ invariant ownedTotalIsSumOfBalances()
|
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
|
*/
|
|
|
invariant balanceOfConsistency(address user)
|
|
|
- to_mathint(balanceOf(user)) == _ownedByUser[user] &&
|
|
|
- to_mathint(balanceOf(user)) == _balances[user]
|
|
|
+ to_mathint(unsafeBalanceOf(user)) == _ownedByUser[user] &&
|
|
|
+ to_mathint(unsafeBalanceOf(user)) == _balances[user]
|
|
|
{
|
|
|
preserved {
|
|
|
require balanceLimited(user);
|
|
@@ -203,7 +215,7 @@ invariant balanceOfConsistency(address user)
|
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
|
*/
|
|
|
invariant ownerHasBalance(uint256 tokenId)
|
|
|
- balanceOf(ownerOf(tokenId)) > 0
|
|
|
+ unsafeOwnerOf(tokenId) == 0 || unsafeBalanceOf(unsafeOwnerOf(tokenId)) > 0
|
|
|
{
|
|
|
preserved {
|
|
|
requireInvariant balanceOfConsistency(ownerOf(tokenId));
|
|
@@ -216,6 +228,9 @@ invariant ownerHasBalance(uint256 tokenId)
|
|
|
│ Rule: balance of address(0) is 0 │
|
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
|
*/
|
|
|
+invariant zeroAddressUnsafeBalanceIsZero()
|
|
|
+ unsafeBalanceOf(0) == 0;
|
|
|
+
|
|
|
rule zeroAddressBalanceRevert() {
|
|
|
balanceOf@withrevert(0);
|
|
|
assert lastReverted;
|
|
@@ -301,9 +316,9 @@ rule balanceChange(env e, address account) {
|
|
|
requireInvariant balanceOfConsistency(account);
|
|
|
require balanceLimited(account);
|
|
|
|
|
|
- mathint balanceBefore = balanceOf(account);
|
|
|
+ mathint balanceBefore = unsafeBalanceOf(account);
|
|
|
method f; helperSoundFnCall(e, f);
|
|
|
- mathint balanceAfter = balanceOf(account);
|
|
|
+ mathint balanceAfter = unsafeBalanceOf(account);
|
|
|
|
|
|
// balance can change by at most 1
|
|
|
assert balanceBefore != balanceAfter => (
|
|
@@ -397,18 +412,20 @@ rule approvedForAllChange(env e, address owner, address spender) {
|
|
|
*/
|
|
|
rule transferFrom(env e, address from, address to, uint256 tokenId) {
|
|
|
require nonpayable(e);
|
|
|
- require authSanity(e);
|
|
|
+ require nonzerosender(e);
|
|
|
|
|
|
address operator = e.msg.sender;
|
|
|
uint256 otherTokenId;
|
|
|
address otherAccount;
|
|
|
|
|
|
+ requireInvariant zeroAddressHasNoApprovedOperator(operator);
|
|
|
+ requireInvariant notMintedUnset(tokenId);
|
|
|
requireInvariant ownerHasBalance(tokenId);
|
|
|
require balanceLimited(to);
|
|
|
|
|
|
- uint256 balanceOfFromBefore = balanceOf(from);
|
|
|
- uint256 balanceOfToBefore = balanceOf(to);
|
|
|
- uint256 balanceOfOtherBefore = balanceOf(otherAccount);
|
|
|
+ uint256 balanceOfFromBefore = unsafeBalanceOf(from);
|
|
|
+ uint256 balanceOfToBefore = unsafeBalanceOf(to);
|
|
|
+ uint256 balanceOfOtherBefore = unsafeBalanceOf(otherAccount);
|
|
|
address ownerBefore = unsafeOwnerOf(tokenId);
|
|
|
address otherOwnerBefore = unsafeOwnerOf(otherTokenId);
|
|
|
address approvalBefore = unsafeGetApproved(tokenId);
|
|
@@ -427,14 +444,14 @@ rule transferFrom(env e, address from, address to, uint256 tokenId) {
|
|
|
|
|
|
// 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
|
|
|
+ to_mathint(unsafeBalanceOf(from)) == balanceOfFromBefore - assert_uint256(from != to ? 1 : 0) &&
|
|
|
+ to_mathint(unsafeBalanceOf(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 unsafeBalanceOf(otherAccount) != balanceOfOtherBefore => (otherAccount == from || otherAccount == to);
|
|
|
assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId;
|
|
|
assert unsafeGetApproved(otherTokenId) != otherApprovalBefore => otherTokenId == tokenId;
|
|
|
}
|
|
@@ -449,25 +466,26 @@ rule safeTransferFrom(env e, method f, address from, address to, uint256 tokenId
|
|
|
f.selector == sig:safeTransferFrom(address,address,uint256,bytes).selector
|
|
|
} {
|
|
|
require nonpayable(e);
|
|
|
- require authSanity(e);
|
|
|
+ require nonzerosender(e);
|
|
|
|
|
|
address operator = e.msg.sender;
|
|
|
uint256 otherTokenId;
|
|
|
address otherAccount;
|
|
|
|
|
|
+ requireInvariant zeroAddressHasNoApprovedOperator(operator);
|
|
|
+ requireInvariant notMintedUnset(tokenId);
|
|
|
requireInvariant ownerHasBalance(tokenId);
|
|
|
require balanceLimited(to);
|
|
|
|
|
|
- uint256 balanceOfFromBefore = balanceOf(from);
|
|
|
- uint256 balanceOfToBefore = balanceOf(to);
|
|
|
- uint256 balanceOfOtherBefore = balanceOf(otherAccount);
|
|
|
+ uint256 balanceOfFromBefore = unsafeBalanceOf(from);
|
|
|
+ uint256 balanceOfToBefore = unsafeBalanceOf(to);
|
|
|
+ uint256 balanceOfOtherBefore = unsafeBalanceOf(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;
|
|
|
+ bool success = helperTransferWithRevert(e, f, from, to, tokenId);
|
|
|
|
|
|
assert success <=> (
|
|
|
from == ownerBefore &&
|
|
@@ -478,14 +496,14 @@ rule safeTransferFrom(env e, method f, address from, address to, uint256 tokenId
|
|
|
|
|
|
// 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
|
|
|
+ to_mathint(unsafeBalanceOf(from)) == balanceOfFromBefore - assert_uint256(from != to ? 1: 0) &&
|
|
|
+ to_mathint(unsafeBalanceOf(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 unsafeBalanceOf(otherAccount) != balanceOfOtherBefore => (otherAccount == from || otherAccount == to);
|
|
|
assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId;
|
|
|
assert unsafeGetApproved(otherTokenId) != otherApprovalBefore => otherTokenId == tokenId;
|
|
|
}
|
|
@@ -505,8 +523,8 @@ rule mint(env e, address to, uint256 tokenId) {
|
|
|
require balanceLimited(to);
|
|
|
|
|
|
mathint supplyBefore = _supply;
|
|
|
- uint256 balanceOfToBefore = balanceOf(to);
|
|
|
- uint256 balanceOfOtherBefore = balanceOf(otherAccount);
|
|
|
+ uint256 balanceOfToBefore = unsafeBalanceOf(to);
|
|
|
+ uint256 balanceOfOtherBefore = unsafeBalanceOf(otherAccount);
|
|
|
address ownerBefore = unsafeOwnerOf(tokenId);
|
|
|
address otherOwnerBefore = unsafeOwnerOf(otherTokenId);
|
|
|
|
|
@@ -521,14 +539,14 @@ rule mint(env e, address to, uint256 tokenId) {
|
|
|
|
|
|
// effect
|
|
|
assert success => (
|
|
|
- _supply == supplyBefore + 1 &&
|
|
|
- to_mathint(balanceOf(to)) == balanceOfToBefore + 1 &&
|
|
|
- unsafeOwnerOf(tokenId) == to
|
|
|
+ _supply == supplyBefore + 1 &&
|
|
|
+ to_mathint(unsafeBalanceOf(to)) == balanceOfToBefore + 1 &&
|
|
|
+ unsafeOwnerOf(tokenId) == to
|
|
|
);
|
|
|
|
|
|
// no side effect
|
|
|
- assert balanceOf(otherAccount) != balanceOfOtherBefore => otherAccount == to;
|
|
|
- assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId;
|
|
|
+ assert unsafeBalanceOf(otherAccount) != balanceOfOtherBefore => otherAccount == to;
|
|
|
+ assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId;
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -549,13 +567,12 @@ rule safeMint(env e, method f, address to, uint256 tokenId) filtered { f ->
|
|
|
require balanceLimited(to);
|
|
|
|
|
|
mathint supplyBefore = _supply;
|
|
|
- uint256 balanceOfToBefore = balanceOf(to);
|
|
|
- uint256 balanceOfOtherBefore = balanceOf(otherAccount);
|
|
|
+ uint256 balanceOfToBefore = unsafeBalanceOf(to);
|
|
|
+ uint256 balanceOfOtherBefore = unsafeBalanceOf(otherAccount);
|
|
|
address ownerBefore = unsafeOwnerOf(tokenId);
|
|
|
address otherOwnerBefore = unsafeOwnerOf(otherTokenId);
|
|
|
|
|
|
- helperMintWithRevert(e, f, to, tokenId);
|
|
|
- bool success = !lastReverted;
|
|
|
+ bool success = helperMintWithRevert(e, f, to, tokenId);
|
|
|
|
|
|
assert success <=> (
|
|
|
ownerBefore == 0 &&
|
|
@@ -564,14 +581,14 @@ rule safeMint(env e, method f, address to, uint256 tokenId) filtered { f ->
|
|
|
|
|
|
// effect
|
|
|
assert success => (
|
|
|
- _supply == supplyBefore + 1 &&
|
|
|
- to_mathint(balanceOf(to)) == balanceOfToBefore + 1 &&
|
|
|
- unsafeOwnerOf(tokenId) == to
|
|
|
+ _supply == supplyBefore + 1 &&
|
|
|
+ to_mathint(unsafeBalanceOf(to)) == balanceOfToBefore + 1 &&
|
|
|
+ unsafeOwnerOf(tokenId) == to
|
|
|
);
|
|
|
|
|
|
// no side effect
|
|
|
- assert balanceOf(otherAccount) != balanceOfOtherBefore => otherAccount == to;
|
|
|
- assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId;
|
|
|
+ assert unsafeBalanceOf(otherAccount) != balanceOfOtherBefore => otherAccount == to;
|
|
|
+ assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId;
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -582,17 +599,16 @@ rule safeMint(env e, method f, address to, uint256 tokenId) filtered { f ->
|
|
|
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 ownerBefore = unsafeOwnerOf(tokenId); // from
|
|
|
address otherOwnerBefore = unsafeOwnerOf(otherTokenId);
|
|
|
+ uint256 balanceOfOwnerBefore = unsafeBalanceOf(ownerBefore);
|
|
|
+ uint256 balanceOfOtherBefore = unsafeBalanceOf(otherAccount);
|
|
|
address otherApprovalBefore = unsafeGetApproved(otherTokenId);
|
|
|
|
|
|
burn@withrevert(e, tokenId);
|
|
@@ -606,13 +622,13 @@ rule burn(env e, uint256 tokenId) {
|
|
|
// effect
|
|
|
assert success => (
|
|
|
_supply == supplyBefore - 1 &&
|
|
|
- to_mathint(balanceOf(from)) == balanceOfFromBefore - 1 &&
|
|
|
+ to_mathint(unsafeBalanceOf(ownerBefore)) == balanceOfOwnerBefore - 1 &&
|
|
|
unsafeOwnerOf(tokenId) == 0 &&
|
|
|
unsafeGetApproved(tokenId) == 0
|
|
|
);
|
|
|
|
|
|
// no side effect
|
|
|
- assert balanceOf(otherAccount) != balanceOfOtherBefore => otherAccount == from;
|
|
|
+ assert unsafeBalanceOf(otherAccount) != balanceOfOtherBefore => otherAccount == ownerBefore;
|
|
|
assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId;
|
|
|
assert unsafeGetApproved(otherTokenId) != otherApprovalBefore => otherTokenId == tokenId;
|
|
|
}
|
|
@@ -624,7 +640,7 @@ rule burn(env e, uint256 tokenId) {
|
|
|
*/
|
|
|
rule approve(env e, address spender, uint256 tokenId) {
|
|
|
require nonpayable(e);
|
|
|
- require authSanity(e);
|
|
|
+ require nonzerosender(e);
|
|
|
|
|
|
address caller = e.msg.sender;
|
|
|
address owner = unsafeOwnerOf(tokenId);
|