Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
0c3b866b
Unverified
Commit
0c3b866b
authored
Apr 04, 2023
by
Madhur Shrimal
Committed by
GitHub
Apr 04, 2023
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'ethereum-optimism:develop' into madhur/tx-metrics
parents
7c2dfa62
44eec0de
Changes
9
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
516 additions
and
159 deletions
+516
-159
.gitmodules
.gitmodules
+5
-0
main.go
op-chain-ops/cmd/rollover/main.go
+22
-8
util.go
op-chain-ops/util/util.go
+4
-4
.gitmodules
packages/contracts-periphery/.gitmodules
+0
-0
MulticallContractCompiler.t.sol
...y/contracts/foundry-tests/MulticallContractCompiler.t.sol
+11
-0
Optimist.t.sol
...ontracts-periphery/contracts/foundry-tests/Optimist.t.sol
+440
-130
Optimist.sol
...ntracts-periphery/contracts/universal/op-nft/Optimist.sol
+29
-17
foundry.toml
packages/contracts-periphery/foundry.toml
+4
-0
multicall
packages/contracts-periphery/lib/multicall
+1
-0
No files found.
.gitmodules
View file @
0c3b866b
[submodule "tests"]
path = l2geth/tests/testdata
url = https://github.com/ethereum/tests
[submodule "packages/contracts-periphery/lib/multicall"]
path = packages/contracts-periphery/lib/multicall
url = https://github.com/mds1/multicall
[submodule "lib/multicall"]
branch = v3.1.0
op-chain-ops/cmd/rollover/main.go
View file @
0c3b866b
...
...
@@ -9,21 +9,25 @@ import (
"sync"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/mattn/go-isatty"
"github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-chain-ops/util"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
legacy_bindings
"github.com/ethereum-optimism/optimism/op-bindings/legacy-bindings"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum-optimism/optimism/op-chain-ops/util"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli/v2"
)
func
main
()
{
log
.
Root
()
.
SetHandler
(
log
.
StreamHandler
(
os
.
Stderr
,
log
.
TerminalFormat
(
isatty
.
IsTerminal
(
os
.
Stderr
.
Fd
()))))
app
:=
cli
.
NewApp
()
app
.
Name
=
"rollover"
app
.
Usage
=
"Commands for assisting in the rollover of the system"
...
...
@@ -149,6 +153,9 @@ func main() {
return
err
}
log
.
Info
(
"Remaining deposits that must be submitted"
,
"count"
,
finalPending
)
if
finalPending
.
Cmp
(
common
.
Big0
)
==
0
{
log
.
Info
(
"All deposits have been batch submitted"
)
}
return
nil
},
},
...
...
@@ -183,11 +190,11 @@ func main() {
log
.
Info
(
"Waiting for CanonicalTransactionChain"
)
wg
.
Add
(
1
)
go
waitForTotalElements
(
&
wg
,
ctc
,
clients
.
L2Client
)
go
waitForTotalElements
(
&
wg
,
ctc
,
clients
.
L2Client
,
"CanonicalTransactionChain"
)
log
.
Info
(
"Waiting for StateCommitmentChain"
)
wg
.
Add
(
1
)
go
waitForTotalElements
(
&
wg
,
scc
,
clients
.
L2Client
)
go
waitForTotalElements
(
&
wg
,
scc
,
clients
.
L2Client
,
"StateCommitmentChain"
)
wg
.
Wait
()
log
.
Info
(
"All batches have been submitted"
)
...
...
@@ -210,7 +217,7 @@ type RollupContract interface {
}
// waitForTotalElements will poll to see
func
waitForTotalElements
(
wg
*
sync
.
WaitGroup
,
contract
RollupContract
,
client
*
ethclient
.
Client
)
{
func
waitForTotalElements
(
wg
*
sync
.
WaitGroup
,
contract
RollupContract
,
client
*
ethclient
.
Client
,
name
string
)
{
defer
wg
.
Done
()
for
{
...
...
@@ -228,9 +235,16 @@ func waitForTotalElements(wg *sync.WaitGroup, contract RollupContract, client *e
}
if
totalElements
.
Uint64
()
==
bn
{
log
.
Info
(
"Total elements matches block number"
,
"name"
,
name
,
"count"
,
bn
)
return
}
log
.
Info
(
"Waiting for elements to be submitted"
,
"count"
,
totalElements
.
Uint64
()
-
bn
,
"height"
,
bn
,
"total-elements"
,
totalElements
.
Uint64
())
log
.
Info
(
"Waiting for elements to be submitted"
,
"name"
,
name
,
"count"
,
totalElements
.
Uint64
()
-
bn
,
"height"
,
bn
,
"total-elements"
,
totalElements
.
Uint64
(),
)
time
.
Sleep
(
3
*
time
.
Second
)
}
...
...
op-chain-ops/util/util.go
View file @
0c3b866b
...
...
@@ -39,21 +39,21 @@ func NewClients(ctx *cli.Context) (*Clients, error) {
l1RpcURL
:=
ctx
.
String
(
"l1-rpc-url"
)
l1Client
,
err
:=
ethclient
.
Dial
(
l1RpcURL
)
if
err
!=
nil
{
return
nil
,
err
return
nil
,
fmt
.
Errorf
(
"cannot dial L1: %w"
,
err
)
}
l1ChainID
,
err
:=
l1Client
.
ChainID
(
context
.
Background
())
if
err
!=
nil
{
return
nil
,
err
return
nil
,
fmt
.
Errorf
(
"cannot fetch L1 chainid: %w"
,
err
)
}
l2RpcURL
:=
ctx
.
String
(
"l2-rpc-url"
)
l2Client
,
err
:=
ethclient
.
Dial
(
l2RpcURL
)
if
err
!=
nil
{
return
nil
,
err
return
nil
,
fmt
.
Errorf
(
"cannot dial L2: %w"
,
err
)
}
l2ChainID
,
err
:=
l2Client
.
ChainID
(
context
.
Background
())
if
err
!=
nil
{
return
nil
,
err
return
nil
,
fmt
.
Errorf
(
"cannot fetch L2 chainid: %w"
,
err
)
}
l1RpcClient
,
err
:=
rpc
.
DialContext
(
context
.
Background
(),
l1RpcURL
)
...
...
packages/contracts-periphery/.gitmodules
0 → 100644
View file @
0c3b866b
packages/contracts-periphery/contracts/foundry-tests/MulticallContractCompiler.t.sol
0 → 100644
View file @
0c3b866b
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { Multicall3 } from "multicall/src/Multicall3.sol";
/**
* Just exists so we can compile this contract.
*/
contract MulticallContractCompiler {
}
packages/contracts-periphery/contracts/foundry-tests/Optimist.t.sol
View file @
0c3b866b
//SPDX-License-Identifier: MIT
pragma solidity
0.8.15
;
pragma solidity
>=0.6.2 <0.9.0
;
/* Testing utilities */
import { Test } from "forge-std/Test.sol";
import { AttestationStation } from "../universal/op-nft/AttestationStation.sol";
import { Optimist } from "../universal/op-nft/Optimist.sol";
import { OptimistAllowlist } from "../universal/op-nft/OptimistAllowlist.sol";
import { OptimistInviter } from "../universal/op-nft/OptimistInviter.sol";
import { OptimistInviterHelper } from "../testing/helpers/OptimistInviterHelper.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
interface IMulticall3 {
struct Call3 {
address target;
bool allowFailure;
bytes callData;
}
struct Result {
bool success;
bytes returnData;
}
function aggregate3(Call3[] calldata calls)
external
payable
returns (Result[] memory returnData);
}
contract Optimist_Initializer is Test {
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Initialized(uint8);
event AttestationCreated(
address indexed creator,
address indexed about,
bytes32 indexed key,
bytes val
);
address constant alice_admin = address(128);
address constant bob = address(256);
address constant sally = address(512);
string constant name = "Optimist name";
string constant symbol = "OPTIMISTSYMBOL";
string constant base_uri =
"https://storageapi.fleek.co/6442819a1b05-bucket/optimist-nft/attributes";
AttestationStation attestationStation;
Optimist optimist;
OptimistAllowlist optimistAllowlist;
OptimistInviter optimistInviter;
// Helps with EIP-712 signature generation
OptimistInviterHelper optimistInviterHelper;
// To test multicall for claiming and minting in one call
IMulticall3 multicall3;
function attestBaseuri(string memory _baseUri) internal {
address internal carol_baseURIAttestor;
address internal alice_allowlistAttestor;
address internal eve_inviteGranter;
address internal ted_coinbaseAttestor;
address internal bob;
address internal sally;
/**
* @notice BaseURI attestor sets the baseURI of the Optimist NFT.
*/
function _attestBaseURI(string memory _baseUri) internal {
bytes32 baseURIAttestationKey = optimist.BASE_URI_ATTESTATION_KEY();
AttestationStation.AttestationData[]
memory attestationData = new AttestationStation.AttestationData[](1);
attestationData[0] = AttestationStation.AttestationData(
address(optimist),
b
ytes32("optimist.base-uri")
,
b
aseURIAttestationKey
,
bytes(_baseUri)
);
vm.prank(alice_admin);
vm.expectEmit(true, true, true, true, address(attestationStation));
emit AttestationCreated(
carol_baseURIAttestor,
address(optimist),
baseURIAttestationKey,
bytes(_baseUri)
);
vm.prank(carol_baseURIAttestor);
attestationStation.attest(attestationData);
}
/**
* @notice Allowlist attestor creates an attestation for an address.
*/
function _attestAllowlist(address _about) internal {
bytes32 attestationKey = optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY();
AttestationStation.AttestationData[]
memory attestationData = new AttestationStation.AttestationData[](1);
// we are using true but it can be any non empty value
attestationData[0] = AttestationStation.AttestationData({
about: _about,
key: attestationKey,
val: bytes("true")
});
vm.expectEmit(true, true, true, true, address(attestationStation));
emit AttestationCreated(alice_allowlistAttestor, _about, attestationKey, bytes("true"));
vm.prank(alice_allowlistAttestor);
attestationStation.attest(attestationData);
assertTrue(optimist.isOnAllowList(_about));
}
function attestAllowlist(address _about) internal {
/**
* @notice Coinbase Quest attestor creates an attestation for an address.
*/
function _attestCoinbaseQuest(address _about) internal {
bytes32 attestationKey = optimistAllowlist.COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY();
AttestationStation.AttestationData[]
memory attestationData = new AttestationStation.AttestationData[](1);
// we are using true but it can be any non empty value
attestationData[0] = AttestationStation.AttestationData({
about: _about,
key:
bytes32("optimist.can-mint")
,
key:
attestationKey
,
val: bytes("true")
});
vm.prank(alice_admin);
vm.expectEmit(true, true, true, true, address(attestationStation));
emit AttestationCreated(ted_coinbaseAttestor, _about, attestationKey, bytes("true"));
vm.prank(ted_coinbaseAttestor);
attestationStation.attest(attestationData);
assertTrue(optimist.isOnAllowList(_about));
}
/**
* @notice Issues invite, then claims it using the claimer's address.
*/
function _inviteAndClaim(address _about) internal {
uint256 inviterPrivateKey = 0xbeefbeef;
address inviter = vm.addr(inviterPrivateKey);
address[] memory addresses = new address[](1);
addresses[0] = inviter;
vm.prank(eve_inviteGranter);
// grant invites to Inviter;
optimistInviter.setInviteCounts(addresses, 3);
// issue a new invite
OptimistInviter.ClaimableInvite memory claimableInvite = optimistInviterHelper
.getClaimableInviteWithNewNonce(inviter);
// EIP-712 sign with Inviter's private key
(uint8 v, bytes32 r, bytes32 s) = vm.sign(
inviterPrivateKey,
optimistInviterHelper.getDigest(claimableInvite)
);
bytes memory signature = abi.encodePacked(r, s, v);
bytes32 hashedCommit = keccak256(abi.encode(_about, signature));
// commit the invite
vm.prank(_about);
optimistInviter.commitInvite(hashedCommit);
// wait minimum commitment period
vm.warp(optimistInviter.MIN_COMMITMENT_PERIOD() + block.timestamp);
// reveal and claim the invite
optimistInviter.claimInvite(_about, claimableInvite, signature);
assertTrue(optimist.isOnAllowList(_about));
}
/**
* @notice Mocks the allowlistAttestor to always return true for a given address.
*/
function _mockAllowlistTrueFor(address _claimer) internal {
vm.mockCall(
address(optimistAllowlist),
abi.encodeWithSelector(OptimistAllowlist.isAllowedToMint.selector, _claimer),
abi.encode(true)
);
assertTrue(optimist.isOnAllowList(_claimer));
}
/**
* @notice Returns address as uint256.
*/
function _getTokenId(address _owner) internal pure returns (uint256) {
return uint256(uint160(address(_owner)));
}
function setUp() public {
// Give alice and bob and sally some ETH
vm.deal(alice_admin, 1 ether);
vm.deal(bob, 1 ether);
vm.deal(sally, 1 ether);
vm.label(alice_admin, "alice_admin");
vm.label(bob, "bob");
vm.label(sally, "sally");
carol_baseURIAttestor = makeAddr("carol_baseURIAttestor");
alice_allowlistAttestor = makeAddr("alice_allowlistAttestor");
eve_inviteGranter = makeAddr("eve_inviteGranter");
ted_coinbaseAttestor = makeAddr("ted_coinbaseAttestor");
bob = makeAddr("bob");
sally = makeAddr("sally");
_initializeContracts();
}
...
...
@@ -63,99 +207,207 @@ contract Optimist_Initializer is Test {
attestationStation = new AttestationStation();
vm.expectEmit(true, true, false, false);
emit Initialized(1);
optimist = new Optimist(name, symbol, alice_admin, attestationStation);
optimistInviter = new OptimistInviter({
_inviteGranter: eve_inviteGranter,
_attestationStation: attestationStation
});
optimistInviter.initialize("OptimistInviter");
// Initialize the helper which helps sign EIP-712 signatures
optimistInviterHelper = new OptimistInviterHelper(optimistInviter, "OptimistInviter");
optimistAllowlist = new OptimistAllowlist({
_attestationStation: attestationStation,
_allowlistAttestor: alice_allowlistAttestor,
_coinbaseQuestAttestor: ted_coinbaseAttestor,
_optimistInviter: address(optimistInviter)
});
optimist = new Optimist({
_name: name,
_symbol: symbol,
_baseURIAttestor: carol_baseURIAttestor,
_attestationStation: attestationStation,
_optimistAllowlist: optimistAllowlist
});
// address test = deployCode("Multicall3.sol");
multicall3 = IMulticall3(deployCode("Multicall3.sol"));
}
}
contract OptimistTest is Optimist_Initializer {
function test_optimist_initialize() external {
/**
* @notice Check that constructor and initializer parameters are correctly set.
*/
function test_initialize_success() external {
// expect name to be set
assertEq(optimist.name(), name);
// expect symbol to be set
assertEq(optimist.symbol(), symbol);
// expect attestationStation to be set
assertEq(address(optimist.ATTESTATION_STATION()), address(attestationStation));
assertEq(optimist.
ATTESTOR(), alice_admin
);
assertEq(optimist.version(), "
1
.0.0");
assertEq(optimist.
BASE_URI_ATTESTOR(), carol_baseURIAttestor
);
assertEq(optimist.version(), "
2
.0.0");
}
/**
* @
dev
Bob should be able to mint an NFT if he is allowlisted
*
by the attestation station and has a balance of 0
* @
notice
Bob should be able to mint an NFT if he is allowlisted
*
by the allowlistAttestor and has a balance of 0.
*/
function test_
optimist_mint_happy_path
() external {
function test_
mint_afterAllowlistAttestation_succeeds
() external {
// bob should start with 0 balance
assertEq(optimist.balanceOf(bob), 0);
// whitelist bob
attestAllowlist(bob);
// allowlist bob
_attestAllowlist(bob);
assertTrue(optimistAllowlist.isAllowedToMint(bob));
// Check that the OptimistAllowlist is checked
bytes memory data = abi.encodeWithSelector(optimistAllowlist.isAllowedToMint.selector, bob);
vm.expectCall(address(optimistAllowlist), data);
uint256 tokenId = uint256(uint160(bob));
// mint an NFT and expect mint transfer event to be emitted
vm.expectEmit(true, true, true, true);
emit Transfer(address(0), bob, tokenId);
emit Transfer(address(0), bob, _getTokenId(bob));
vm.prank(bob);
optimist.mint(bob);
bytes memory data = abi.encodeWithSelector(
attestationStation.attestations.selector,
alice_admin,
bob,
bytes32("optimist.can-mint")
);
vm.expectCall(address(attestationStation), data);
// mint an NFT
// expect the NFT to be owned by bob
assertEq(optimist.ownerOf(_getTokenId(bob)), bob);
assertEq(optimist.balanceOf(bob), 1);
}
/**
* @notice Bob should be able to mint an NFT if he claimed an invite through OptimistInviter
* and has a balance of 0.
*/
function test_mint_afterInviteClaimed_succeeds() external {
// bob should start with 0 balance
assertEq(optimist.balanceOf(bob), 0);
// bob claims an invite
_inviteAndClaim(bob);
assertTrue(optimistAllowlist.isAllowedToMint(bob));
// Check that the OptimistAllowlist is checked
bytes memory data = abi.encodeWithSelector(optimistAllowlist.isAllowedToMint.selector, bob);
vm.expectCall(address(optimistAllowlist), data);
// mint an NFT and expect mint transfer event to be emitted
vm.expectEmit(true, true, true, true);
emit Transfer(address(0), bob, _getTokenId(bob));
vm.prank(bob);
optimist.mint(bob);
// expect the NFT to be owned by bob
assertEq(optimist.ownerOf(
256
), bob);
assertEq(optimist.ownerOf(
_getTokenId(bob)
), bob);
assertEq(optimist.balanceOf(bob), 1);
}
/**
* @dev Sally should be able to mint a token on behalf of bob
* @notice Bob should be able to mint an NFT if he has an attestation from Coinbase Quest
* attestor and has a balance of 0.
*/
function test_optimist_mint_secondary_minter() external {
attestAllowlist(bob);
function test_mint_afterCoinbaseQuestAttestation_succeeds() external {
// bob should start with 0 balance
assertEq(optimist.balanceOf(bob), 0);
bytes memory data = abi.encodeWithSelector(
attestationStation.attestations.selector,
alice_admin,
bob,
bytes32("optimist.can-mint")
);
vm.expectCall(address(attestationStation), data);
// bob receives attestation from Coinbase Quest attestor
_attestCoinbaseQuest(bob);
assertTrue(optimistAllowlist.isAllowedToMint(bob));
// Check that the OptimistAllowlist is checked
bytes memory data = abi.encodeWithSelector(optimistAllowlist.isAllowedToMint.selector, bob);
vm.expectCall(address(optimistAllowlist), data);
// mint an NFT and expect mint transfer event to be emitted
vm.expectEmit(true, true, true, true);
emit Transfer(address(0), bob, _getTokenId(bob));
vm.prank(bob);
optimist.mint(bob);
// expect the NFT to be owned by bob
assertEq(optimist.ownerOf(_getTokenId(bob)), bob);
assertEq(optimist.balanceOf(bob), 1);
}
/**
* @notice Multiple valid attestations should allow Bob to mint.
*/
function test_mint_afterMultipleAttestations_succeeds() external {
// bob should start with 0 balance
assertEq(optimist.balanceOf(bob), 0);
// bob receives attestation from Coinbase Quest attestor
_attestCoinbaseQuest(bob);
// allowlist bob
_attestAllowlist(bob);
// bob claims an invite
_inviteAndClaim(bob);
assertTrue(optimistAllowlist.isAllowedToMint(bob));
// Check that the OptimistAllowlist is checked
bytes memory data = abi.encodeWithSelector(optimistAllowlist.isAllowedToMint.selector, bob);
vm.expectCall(address(optimistAllowlist), data);
// mint an NFT and expect mint transfer event to be emitted
vm.expectEmit(true, true, true, true);
emit Transfer(address(0), bob, _getTokenId(bob));
vm.prank(bob);
optimist.mint(bob);
// expect the NFT to be owned by bob
assertEq(optimist.ownerOf(_getTokenId(bob)), bob);
assertEq(optimist.balanceOf(bob), 1);
}
/**
* @notice Sally should be able to mint a token on behalf of bob.
*/
function test_mint_secondaryMinter_succeeds() external {
_mockAllowlistTrueFor(bob);
uint256 tokenId = uint256(uint160(bob));
vm.expectEmit(true, true, true, true);
emit Transfer(address(0), bob,
tokenId
);
emit Transfer(address(0), bob,
_getTokenId(bob)
);
// mint as sally instead of bob
vm.prank(sally);
optimist.mint(bob);
// expect the NFT to be owned by bob
assertEq(optimist.ownerOf(
256
), bob);
assertEq(optimist.ownerOf(
_getTokenId(bob)
), bob);
assertEq(optimist.balanceOf(bob), 1);
}
/**
* @
dev Bob should not be able to mint an NFT if he is not whitelisted
* @
notice Bob should not be able to mint an NFT if he is not allowlisted.
*/
function test_
optimist_mint_no_attestation
() external {
function test_
mint_forNonAllowlistedClaimer_reverts
() external {
vm.prank(bob);
vm.expectRevert("Optimist: address is not on allowList");
optimist.mint(bob);
}
/**
* @
dev Bob's tx should revert if he already minted
* @
notice Bob's tx should revert if he already minted.
*/
function test_
optimist_mint_already_minted
() external {
attestAllowlist(bob);
function test_
mint_forAlreadyMintedClaimer_reverts
() external {
_
attestAllowlist(bob);
// mint initial nft with bob
vm.prank(bob);
optimist.mint(bob);
// expect the NFT to be owned by bob
assertEq(optimist.ownerOf(
256
), bob);
assertEq(optimist.ownerOf(
_getTokenId(bob)
), bob);
assertEq(optimist.balanceOf(bob), 1);
// attempt to mint again
...
...
@@ -164,82 +416,52 @@ contract OptimistTest is Optimist_Initializer {
}
/**
* @dev The baseURI should be set by attestation station
* by the owner of contract alice_admin
* @notice The baseURI should be set by attestation station by the baseURIAttestor.
*/
function test_
optimist_baseURI
() external {
attestBaseuri
(base_uri);
function test_
baseURI_returnsCorrectBaseURI_succeeds
() external {
_attestBaseURI
(base_uri);
bytes memory data = abi.encodeWithSelector(
attestationStation.attestations.selector,
alice_admin
,
carol_baseURIAttestor
,
address(optimist),
bytes32("optimist.base-uri"
)
optimist.BASE_URI_ATTESTATION_KEY(
)
);
vm.expectCall(address(attestationStation), data);
vm.prank(
alice_admin
);
vm.prank(
carol_baseURIAttestor
);
// assert baseURI is set
assertEq(optimist.baseURI(), base_uri);
}
/**
* @dev The tokenURI should return the token uri
* for a minted token
* @notice tokenURI should return the token uri for a minted token.
*/
function test_optimist_token_uri() external {
attestAllowlist(bob);
function test_tokenURI_returnsCorrectTokenURI_succeeds() external {
// we are using true but it can be any non empty value
attestBaseuri
(base_uri);
_attestBaseURI
(base_uri);
// mint an NFT
_mockAllowlistTrueFor(bob);
vm.prank(bob);
optimist.mint(bob);
// assert tokenURI is set
assertEq(optimist.baseURI(), base_uri);
assertEq(
optimist.tokenURI(256),
// solhint-disable-next-line max-line-length
"https://storageapi.fleek.co/6442819a1b05-bucket/optimist-nft/attributes/0x0000000000000000000000000000000000000100.json"
);
}
/**
* @dev Should return a boolean of if the address is whitelisted
*/
function test_optimist_is_on_allow_list() external {
attestAllowlist(bob);
bytes memory data = abi.encodeWithSelector(
attestationStation.attestations.selector,
alice_admin,
bob,
bytes32("optimist.can-mint")
optimist.tokenURI(_getTokenId(bob)),
"https://storageapi.fleek.co/6442819a1b05-bucket/optimist-nft/attributes/0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e.json"
);
vm.expectCall(address(attestationStation), data);
// assert bob is whitelisted
assertEq(optimist.isOnAllowList(bob), true);
data = abi.encodeWithSelector(
attestationStation.attestations.selector,
alice_admin,
sally,
bytes32("optimist.can-mint")
);
vm.expectCall(address(attestationStation), data);
// assert sally is not whitelisted
assertEq(optimist.isOnAllowList(sally), false);
}
/**
* @
dev Should return the token id of the owner
* @
notice Should return the token id of the owner.
*/
function test_optimist_token_id_of_owner() external {
// whitelist bob
function test_tokenIdOfAddress_returnsOwnerID_succeeds() external {
uint256 willTokenId = 1024;
address will = address(1024);
attestAllowlist
(will);
_mockAllowlistTrueFor
(will);
optimist.mint(will);
...
...
@@ -247,10 +469,10 @@ contract OptimistTest is Optimist_Initializer {
}
/**
* @
dev It should revert if anybody attemps token transfer
* @
notice transferFrom should revert since Optimist is a SBT.
*/
function test_
optimist_sbt_transfer
() external {
attestAllowlist
(bob);
function test_
transferFrom_reverts
() external {
_mockAllowlistTrueFor
(bob);
// mint as bob
vm.prank(bob);
...
...
@@ -259,23 +481,23 @@ contract OptimistTest is Optimist_Initializer {
// attempt to transfer to sally
vm.expectRevert(bytes("Optimist: soul bound token"));
vm.prank(bob);
optimist.transferFrom(bob, sally,
256
);
optimist.transferFrom(bob, sally,
_getTokenId(bob)
);
// attempt to transfer to sally
vm.expectRevert(bytes("Optimist: soul bound token"));
vm.prank(bob);
optimist.safeTransferFrom(bob, sally,
256
);
optimist.safeTransferFrom(bob, sally,
_getTokenId(bob)
);
// attempt to transfer to sally
vm.expectRevert(bytes("Optimist: soul bound token"));
vm.prank(bob);
optimist.safeTransferFrom(bob, sally,
256
, bytes("0x"));
optimist.safeTransferFrom(bob, sally,
_getTokenId(bob)
, bytes("0x"));
}
/**
* @
dev It should revert if anybody attemps approve
* @
notice approve should revert since Optimist is a SBT.
*/
function test_
optimist_sbt_approve
() external {
attestAllowlist
(bob);
function test_
approve_reverts
() external {
_mockAllowlistTrueFor
(bob);
// mint as bob
vm.prank(bob);
...
...
@@ -284,16 +506,38 @@ contract OptimistTest is Optimist_Initializer {
// attempt to approve sally
vm.prank(bob);
vm.expectRevert("Optimist: soul bound token");
optimist.approve(address(attestationStation), 256);
optimist.approve(address(attestationStation), _getTokenId(bob));
assertEq(optimist.getApproved(_getTokenId(bob)), address(0));
}
/**
* @notice setApprovalForAll should revert since Optimist is a SBT.
*/
function test_setApprovalForAll_reverts() external {
_mockAllowlistTrueFor(bob);
// mint as bob
vm.prank(bob);
optimist.mint(bob);
vm.prank(alice_allowlistAttestor);
vm.expectRevert(bytes("Optimist: soul bound token"));
optimist.setApprovalForAll(alice_allowlistAttestor, true);
assertEq(optimist.getApproved(256), address(0));
// expect approval amount to stil be 0
assertEq(optimist.getApproved(_getTokenId(bob)), address(0));
// isApprovedForAll should return false
assertEq(
optimist.isApprovedForAll(alice_allowlistAttestor, alice_allowlistAttestor),
false
);
}
/**
* @
dev It should be able to burn token
* @
notice Only owner should be able to burn token.
*/
function test_
optimist_burn
() external {
attestAllowlist
(bob);
function test_
burn_byOwner_succeeds
() external {
_mockAllowlistTrueFor
(bob);
// mint as bob
vm.prank(bob);
...
...
@@ -301,37 +545,103 @@ contract OptimistTest is Optimist_Initializer {
// burn as bob
vm.prank(bob);
optimist.burn(
256
);
optimist.burn(
_getTokenId(bob)
);
// expect bob to have no balance now
assertEq(optimist.balanceOf(bob), 0);
}
/**
* @
dev setApprovalForAll should revert as sbt
* @
notice Non-owner attempting to burn token should revert.
*/
function test_
optimist_set_approval_for_all
() external {
attestAllowlist
(bob);
function test_
burn_byNonOwner_reverts
() external {
_mockAllowlistTrueFor
(bob);
// mint as bob
vm.prank(bob);
optimist.mint(bob);
vm.prank(alice_admin);
vm.expectRevert(bytes("Optimist: soul bound token"));
optimist.setApprovalForAll(alice_admin, true);
// expect approval amount to stil be 0
assertEq(optimist.getApproved(256), address(0));
// isApprovedForAll should return false
assertEq(optimist.isApprovedForAll(alice_admin, alice_admin), false);
vm.expectRevert("ERC721: caller is not token owner nor approved");
// burn as Sally
vm.prank(sally);
optimist.burn(_getTokenId(bob));
// expect bob to have still have the token
assertEq(optimist.balanceOf(bob), 1);
}
/**
* @
dev should support erc721 interface
* @
notice Should support ERC-721 interface.
*/
function test_
optimist_supports_interface
() external {
function test_
supportsInterface_returnsCorrectInterfaceForERC721_succeeds
() external {
bytes4 iface721 = type(IERC721).interfaceId;
// check that it supports
erc
721 interface
// check that it supports
ERC-
721 interface
assertEq(optimist.supportsInterface(iface721), true);
}
/**
* @notice Checking that multi-call using the invite & claim flow works correctly, since the
* frontend will be making multicalls to improve UX. The OptimistInviter.claimInvite
* and Optimist.mint will be batched
*/
function test_multicall_batchingClaimAndMint_succeeds() external {
uint256 inviterPrivateKey = 0xbeefbeef;
address inviter = vm.addr(inviterPrivateKey);
address[] memory addresses = new address[](1);
addresses[0] = inviter;
vm.prank(eve_inviteGranter);
// grant invites to Inviter;
optimistInviter.setInviteCounts(addresses, 3);
// issue a new invite
OptimistInviter.ClaimableInvite memory claimableInvite = optimistInviterHelper
.getClaimableInviteWithNewNonce(inviter);
// EIP-712 sign with Inviter's private key
(uint8 v, bytes32 r, bytes32 s) = vm.sign(
inviterPrivateKey,
optimistInviterHelper.getDigest(claimableInvite)
);
bytes memory signature = abi.encodePacked(r, s, v);
bytes32 hashedCommit = keccak256(abi.encode(bob, signature));
// commit the invite
vm.prank(bob);
optimistInviter.commitInvite(hashedCommit);
// wait minimum commitment period
vm.warp(optimistInviter.MIN_COMMITMENT_PERIOD() + block.timestamp);
IMulticall3.Call3[] memory calls = new IMulticall3.Call3[](2);
// First call is to claim the invite, receiving the attestation
calls[0] = IMulticall3.Call3({
target: address(optimistInviter),
callData: abi.encodeWithSelector(
optimistInviter.claimInvite.selector,
bob,
claimableInvite,
signature
),
allowFailure: false
});
// Second call is to mint the Optimist NFT
calls[1] = IMulticall3.Call3({
target: address(optimist),
callData: abi.encodeWithSelector(optimist.mint.selector, bob),
allowFailure: false
});
multicall3.aggregate3(calls);
assertTrue(optimist.isOnAllowList(bob));
assertEq(optimist.ownerOf(_getTokenId(bob)), bob);
assertEq(optimist.balanceOf(bob), 1);
}
}
packages/contracts-periphery/contracts/universal/op-nft/Optimist.sol
View file @
0c3b866b
...
...
@@ -6,6 +6,7 @@ import {
ERC721BurnableUpgradeable
} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
import { AttestationStation } from "./AttestationStation.sol";
import { OptimistAllowlist } from "./OptimistAllowlist.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
/**
...
...
@@ -15,31 +16,44 @@ import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
* @notice A Soul Bound Token for real humans only(tm).
*/
contract Optimist is ERC721BurnableUpgradeable, Semver {
/**
* @notice Attestation key used by the attestor to attest the baseURI.
*/
bytes32 public constant BASE_URI_ATTESTATION_KEY = bytes32("optimist.base-uri");
/**
* @notice Attestor who attests to baseURI.
*/
address public immutable BASE_URI_ATTESTOR;
/**
* @notice Address of the AttestationStation contract.
*/
AttestationStation public immutable ATTESTATION_STATION;
/**
* @notice A
ttestor who attests to baseURI and allowlis
t.
* @notice A
ddress of the OptimistAllowlist contrac
t.
*/
address public immutable ATTESTOR
;
OptimistAllowlist public immutable OPTIMIST_ALLOWLIST
;
/**
* @custom:semver
1
.0.0
* @custom:semver
2
.0.0
* @param _name Token name.
* @param _symbol Token symbol.
* @param _
attestor Address of the
attestor.
* @param _
baseURIAttestor Address of the baseURI
attestor.
* @param _attestationStation Address of the AttestationStation contract.
* @param _optimistAllowlist Address of the OptimistAllowlist contract
*/
constructor(
string memory _name,
string memory _symbol,
address _attestor,
AttestationStation _attestationStation
) Semver(1, 0, 0) {
ATTESTOR = _attestor;
address _baseURIAttestor,
AttestationStation _attestationStation,
OptimistAllowlist _optimistAllowlist
) Semver(2, 0, 0) {
BASE_URI_ATTESTOR = _baseURIAttestor;
ATTESTATION_STATION = _attestationStation;
OPTIMIST_ALLOWLIST = _optimistAllowlist;
initialize(_name, _symbol);
}
...
...
@@ -76,7 +90,7 @@ contract Optimist is ERC721BurnableUpgradeable, Semver {
string(
abi.encodePacked(
ATTESTATION_STATION.attestations(
ATTESTOR,
BASE_URI_
ATTESTOR,
address(this),
bytes32("optimist.base-uri")
)
...
...
@@ -105,17 +119,15 @@ contract Optimist is ERC721BurnableUpgradeable, Semver {
}
/**
* @notice Checks whether a given address is allowed to mint the Optimist NFT yet. Since the
* Optimist NFT will also be used as part of the Citizens House, mints are currently
* restricted. Eventually anyone will be able to mint.
* @notice Checks OptimistAllowlist to determine whether a given address is allowed to mint
* the Optimist NFT. Since the Optimist NFT will also be used as part of the
* Citizens House, mints are currently restricted. Eventually anyone will be able
* to mint.
*
* @return Whether or not the address is allowed to mint yet.
*/
function isOnAllowList(address _recipient) public view returns (bool) {
return
ATTESTATION_STATION
.attestations(ATTESTOR, _recipient, bytes32("optimist.can-mint"))
.length > 0;
return OPTIMIST_ALLOWLIST.isAllowedToMint(_recipient);
}
/**
...
...
packages/contracts-periphery/foundry.toml
View file @
0c3b866b
...
...
@@ -16,9 +16,13 @@ remappings = [
'@rari-capital/solmate/=node_modules/@rari-capital/solmate'
,
'forge-std/=node_modules/forge-std/src'
,
'ds-test/=node_modules/ds-test/src'
,
'multicall/=lib/multicall'
,
'@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/'
,
'@openzeppelin/contracts-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/'
,
'@eth-optimism/contracts-bedrock/=../../node_modules/@eth-optimism/contracts-bedrock'
,
]
# The metadata hash can be removed from the bytecode by setting "none"
bytecode_hash
=
"none"
libs
=
[
"node_modules"
,
"lib"
]
# Required to use `deployCode` to deploy the multicall contract which has incompatible version
fs_permissions
=
[
{
access
=
"read"
,
path
=
"./forge-artifacts/Multicall3.sol/Multicall3.json"
}
]
multicall
@
a1fa0644
Subproject commit a1fa0644fa412cd3237ef7081458ecb2ffad7dbe
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment