-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- ERC721Enumerable+Pausable inheritance - utils/OwnerPausable (new contract) inheritance for pause() toggling only by owner - Gas-free OpenSea listings thanks to @dievardump's BaseOpenSea
- Loading branch information
Divergence
committed
Nov 21, 2021
1 parent
8ecf18c
commit acc59a6
Showing
9 changed files
with
388 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// SPDX-License-Identifier: MIT | ||
// Copyright Simon Fremaux (@dievardump) | ||
// https://gist.github.com/dievardump/483eb43bc6ed30b14f01e01842e3339b/ | ||
pragma solidity ^0.8.0; | ||
|
||
/// @title OpenSea contract helper that defines a few things | ||
/// @author Simon Fremaux (@dievardump) | ||
/// @dev This is a contract used to add OpenSea's support for gas-less trading | ||
/// by checking if operator is owner's proxy | ||
contract BaseOpenSea { | ||
string private _contractURI; | ||
ProxyRegistry private _proxyRegistry; | ||
|
||
/// @notice Returns the contract URI function. Used on OpenSea to get details | ||
/// about a contract (owner, royalties etc...) | ||
/// See documentation: https://docs.opensea.io/docs/contract-level-metadata | ||
function contractURI() public view returns (string memory) { | ||
return _contractURI; | ||
} | ||
|
||
/// @notice Helper for OpenSea gas-less trading | ||
/// @dev Allows to check if `operator` is owner's OpenSea proxy | ||
/// @param owner the owner we check for | ||
/// @param operator the operator (proxy) we check for | ||
function isOwnersOpenSeaProxy(address owner, address operator) | ||
public | ||
view | ||
returns (bool) | ||
{ | ||
ProxyRegistry proxyRegistry = _proxyRegistry; | ||
return | ||
// we have a proxy registry address | ||
address(proxyRegistry) != address(0) && | ||
// current operator is owner's proxy address | ||
address(proxyRegistry.proxies(owner)) == operator; | ||
} | ||
|
||
/// @dev Internal function to set the _contractURI | ||
/// @param contractURI_ the new contract uri | ||
function _setContractURI(string memory contractURI_) internal { | ||
_contractURI = contractURI_; | ||
} | ||
|
||
/// @dev Internal function to set the _proxyRegistry | ||
/// @param proxyRegistryAddress the new proxy registry address | ||
function _setOpenSeaRegistry(address proxyRegistryAddress) internal { | ||
_proxyRegistry = ProxyRegistry(proxyRegistryAddress); | ||
} | ||
} | ||
|
||
contract OwnableDelegateProxy {} | ||
|
||
contract ProxyRegistry { | ||
mapping(address => OwnableDelegateProxy) public proxies; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
// SPDX-License-Identifier: MIT | ||
// Copyright (c) 2021 Divergent Technologies Ltd (github.com/divergencetech) | ||
pragma solidity >=0.8.0 <0.9.0; | ||
|
||
import "./BaseOpenSea.sol"; | ||
import "../utils/OwnerPausable.sol"; | ||
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; | ||
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Pausable.sol"; | ||
|
||
/** | ||
@notice An ERC721 contract with common functionality: | ||
- OpenSea gas-free listings | ||
- OpenZeppelin Enumerable and Pausable | ||
- OpenZeppelin Pausable with functions exposed to Owner only | ||
*/ | ||
contract ERC721Common is | ||
BaseOpenSea, | ||
ERC721Enumerable, | ||
ERC721Pausable, | ||
OwnerPausable | ||
{ | ||
/** | ||
@param openSeaProxyRegistry Set to | ||
0xa5409ec958c83c3f309868babaca7c86dcb077c1 for Mainnet and | ||
0xf57b2c51ded3a29e6891aba85459d600256cf317 for Rinkeby. | ||
*/ | ||
constructor( | ||
string memory name, | ||
string memory symbol, | ||
address openSeaProxyRegistry | ||
) ERC721(name, symbol) { | ||
if (openSeaProxyRegistry != address(0)) { | ||
BaseOpenSea._setOpenSeaRegistry(openSeaProxyRegistry); | ||
} | ||
} | ||
|
||
/// @notice Requires that the token exists. | ||
modifier tokenExists(uint256 tokenId) { | ||
require(ERC721._exists(tokenId), "ERC721Common: Token doesn't exist"); | ||
_; | ||
} | ||
|
||
/// @notice Requires that msg.sender owns or is approved for the token. | ||
modifier onlyApprovedOrOwner(uint256 tokenId) { | ||
require( | ||
_isApprovedOrOwner(msg.sender, tokenId), | ||
"ERC721Common: Not approved nor owner" | ||
); | ||
_; | ||
} | ||
|
||
/// @notice Overrides _beforeTokenTransfer as required by inheritance. | ||
function _beforeTokenTransfer( | ||
address from, | ||
address to, | ||
uint256 tokenId | ||
) internal virtual override(ERC721Enumerable, ERC721Pausable) { | ||
super._beforeTokenTransfer(from, to, tokenId); | ||
} | ||
|
||
/// @notice Overrides supportsInterface as required by inheritance. | ||
function supportsInterface(bytes4 interfaceId) | ||
public | ||
view | ||
override(ERC721, ERC721Enumerable) | ||
returns (bool) | ||
{ | ||
return super.supportsInterface(interfaceId); | ||
} | ||
|
||
/** | ||
@notice Returns true if either standard isApprovedForAll() returns true or | ||
the operator is the OpenSea proxy for the owner. | ||
*/ | ||
function isApprovedForAll(address owner, address operator) | ||
public | ||
view | ||
override | ||
returns (bool) | ||
{ | ||
return | ||
super.isApprovedForAll(owner, operator) || | ||
BaseOpenSea.isOwnersOpenSeaProxy(owner, operator); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// SPDX-License-Identifier: MIT | ||
// Copyright (c) 2021 Divergent Technologies Ltd (github.com/divergencetech) | ||
pragma solidity >=0.8.0 <0.9.0; | ||
|
||
import "@openzeppelin/contracts/access/Ownable.sol"; | ||
import "@openzeppelin/contracts/security/Pausable.sol"; | ||
|
||
/// @notice A Pausable contract that can only be toggled by the Owner. | ||
contract OwnerPausable is Ownable, Pausable { | ||
/// @notice Pauses the contract. | ||
function pause() public onlyOwner { | ||
Pausable._pause(); | ||
} | ||
|
||
/// @notice Unpauses the contract. | ||
function unpause() public onlyOwner { | ||
Pausable._unpause(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// SPDX-License-Identifier: MIT | ||
// Copyright (c) 2021 Divergent Technologies Ltd (github.com/divergencetech) | ||
pragma solidity >=0.8.0 <0.9.0; | ||
|
||
import "../../contracts/erc721/ERC721Common.sol"; | ||
|
||
/** | ||
@notice Exposes a buy() function to allow testing of DutchAuction and, by proxy, | ||
Seller. | ||
@dev Setting the price decrease of the DutchAuction to zero is identical to a | ||
constant Seller. Creating only a single Testable contract is simpler. | ||
*/ | ||
contract TestableERC721Common is ERC721Common { | ||
constructor() ERC721Common("Token", "JRR", address(0)) {} | ||
|
||
function mint(uint256 tokenId) public { | ||
ERC721._safeMint(msg.sender, tokenId); | ||
} | ||
|
||
/// @dev For testing the tokenExists() modifier. | ||
function mustExist(uint256 tokenId) public view tokenExists(tokenId) {} | ||
|
||
/// @dev For testing the onlyApprovedOrOwner() modifier. | ||
function mustBeApprovedOrOwner(uint256 tokenId) | ||
public | ||
onlyApprovedOrOwner(tokenId) | ||
{} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package erc721_test | ||
|
||
import ( | ||
"math/big" | ||
"testing" | ||
|
||
"github.com/divergencetech/ethier/ethtest" | ||
"github.com/ethereum/go-ethereum/accounts/abi/bind" | ||
"github.com/h-fam/errdiff" | ||
) | ||
|
||
// Actors in the tests | ||
const ( | ||
owner = iota | ||
tokenOwner | ||
approved | ||
vandal | ||
) | ||
|
||
// Token IDs | ||
const ( | ||
exists = iota | ||
notExists | ||
) | ||
|
||
func deploy(t *testing.T) (*ethtest.SimulatedBackend, *TestableERC721Common) { | ||
t.Helper() | ||
|
||
sim := ethtest.NewSimulatedBackendTB(t, 4) | ||
_, _, nft, err := DeployTestableERC721Common(sim.Acc(owner), sim) | ||
if err != nil { | ||
t.Fatalf("DeployTestableERC721Common() error %v", err) | ||
} | ||
|
||
if _, err := nft.Mint(sim.Acc(tokenOwner), big.NewInt(exists)); err != nil { | ||
t.Fatalf("Mint(%d) error %v", exists, err) | ||
} | ||
if _, err := nft.Approve(sim.Acc(tokenOwner), sim.Acc(approved).From, big.NewInt(exists)); err != nil { | ||
t.Fatalf("Approve(<approved account>, %d) error %v", exists, err) | ||
} | ||
|
||
return sim, nft | ||
} | ||
|
||
func TestModifiers(t *testing.T) { | ||
_, nft := deploy(t) | ||
|
||
tests := []struct { | ||
name string | ||
tokenID int64 | ||
errDiffAgainst interface{} | ||
}{ | ||
{ | ||
name: "existing token", | ||
tokenID: exists, | ||
errDiffAgainst: nil, | ||
}, | ||
{ | ||
name: "non-existent token", | ||
tokenID: notExists, | ||
errDiffAgainst: "ERC721Common: Token doesn't exist", | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
err := nft.MustExist(nil, big.NewInt(tt.tokenID)) | ||
if diff := errdiff.Check(err, tt.errDiffAgainst); diff != "" { | ||
t.Errorf("MustExist([%s]), modified with tokenExists(); %s", tt.name, diff) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestOnlyApprovedOrOwner(t *testing.T) { | ||
sim, nft := deploy(t) | ||
|
||
tests := []struct { | ||
name string | ||
account *bind.TransactOpts | ||
errDiffAgainst interface{} | ||
}{ | ||
{ | ||
name: "token owner", | ||
account: sim.Acc(tokenOwner), | ||
errDiffAgainst: nil, | ||
}, | ||
{ | ||
name: "approved", | ||
account: sim.Acc(approved), | ||
errDiffAgainst: nil, | ||
}, | ||
{ | ||
name: "vandal", | ||
account: sim.Acc(vandal), | ||
errDiffAgainst: "ERC721Common: Not approved nor owner", | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
_, err := nft.MustBeApprovedOrOwner(tt.account, big.NewInt(exists)) | ||
if diff := errdiff.Check(err, tt.errDiffAgainst); diff != "" { | ||
t.Errorf("MustBeApprovedOrOwner([%s]), modified with onlyApprovedOrOwner(); %s", tt.name, diff) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package erc721_test | ||
|
||
//go:generate sh -c "solc TestableERC721Common.sol --base-path ../../ --include-path ../../node_modules --combined-json abi,bin | abigen --combined-json /dev/stdin --pkg erc721_test --out generated_test.go" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package utils_test | ||
|
||
//go:generate sh -c "solc ../../contracts/utils/OwnerPausable.sol --base-path ../../ --include-path ../../node_modules --combined-json abi,bin | abigen --combined-json /dev/stdin --pkg utils_test --out generated_test.go" |
Oops, something went wrong.