Commit 6921c386 authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into feat/rollover-scripts-cleanup

parents b7230930 7f5801ec
---
'@eth-optimism/data-transport-layer': patch
---
Add better logging to DTL about shutoff block
---
'@eth-optimism/contracts-bedrock': patch
---
Makes the Proxy contract inheritable by making functions (public virtual).
...@@ -31,12 +31,13 @@ ...@@ -31,12 +31,13 @@
/op-proposer @ethereum-optimism/go-reviewers /op-proposer @ethereum-optimism/go-reviewers
/op-program @ethereum-optimism/go-reviewers /op-program @ethereum-optimism/go-reviewers
/op-service @ethereum-optimism/go-reviewers /op-service @ethereum-optimism/go-reviewers
/ops-bedrock @ethereum-optimism/go-reviewers
# Ops # Ops
/.circleci @ethereum-optimism/infra-reviewers /.circleci @ethereum-optimism/infra-reviewers
/.github @ethereum-optimism/infra-reviewers /.github @ethereum-optimism/infra-reviewers
/ops @ethereum-optimism/infra-reviewers /ops @ethereum-optimism/infra-reviewers
/ops-bedrock @ethereum-optimism/infra-reviewers
/op-signer @ethereum-optimism/infra-reviewers /op-signer @ethereum-optimism/infra-reviewers
# Misc # Misc
......
...@@ -24,7 +24,7 @@ If you want to build Optimism, check out the [Protocol Specs](./specs/). ...@@ -24,7 +24,7 @@ If you want to build Optimism, check out the [Protocol Specs](./specs/).
## Community ## Community
General discussion happens most frequently on the [Optimism discord](https://discord-gateway.optimism.io). General discussion happens most frequently on the [Optimism discord](https://discord.gg/optimism).
Governance discussion can also be found on the [Optimism Governance Forum](https://gov.optimism.io/). Governance discussion can also be found on the [Optimism Governance Forum](https://gov.optimism.io/).
## Contributing ## Contributing
......
# The OP Stack Docs # The OP Stack Docs
[![Discord](https://img.shields.io/discord/667044843901681675.svg?color=768AD4&label=discord&logo=https%3A%2F%2Fdiscordapp.com%2Fassets%2F8c9701b98ad4372b58f13fd9f65f966e.svg)](https://discord-gateway.optimism.io) [![Discord](https://img.shields.io/discord/667044843901681675.svg?color=768AD4&label=discord&logo=https%3A%2F%2Fdiscordapp.com%2Fassets%2F8c9701b98ad4372b58f13fd9f65f966e.svg)](https://discord.gg/optimism)
[![Twitter Follow](https://img.shields.io/twitter/follow/optimismPBC.svg?label=optimismPBC&style=social)](https://twitter.com/optimismPBC) [![Twitter Follow](https://img.shields.io/twitter/follow/optimismPBC.svg?label=optimismPBC&style=social)](https://twitter.com/optimismPBC)
The OP Stack is an open, collectively maintained development stack for blockchain ecosystems. The OP Stack is an open, collectively maintained development stack for blockchain ecosystems.
......
...@@ -57,7 +57,7 @@ export default Vue.extend({ ...@@ -57,7 +57,7 @@ export default Vue.extend({
"Support" "Support"
]), ]),
h("div", { class: "anchor-support-links" }, [ h("div", { class: "anchor-support-links" }, [
h("a", { attrs: { href: "https://discord.optimism.io", target: "_blank" } }, [ h("a", { attrs: { href: "https://discord.gg/optimism", target: "_blank" } }, [
h("div", [ h("div", [
h("i", { attrs: { class: "fab fa-discord" } }), h("i", { attrs: { class: "fab fa-discord" } }),
" Discord community " " Discord community "
......
...@@ -30,4 +30,4 @@ If you’re looking for other ways to get involved, here are a few options: ...@@ -30,4 +30,4 @@ If you’re looking for other ways to get involved, here are a few options:
- Grab an idea from the [project ideas list](https://github.com/ethereum-optimism/optimism-project-ideas) to and building - Grab an idea from the [project ideas list](https://github.com/ethereum-optimism/optimism-project-ideas) to and building
- Suggest a new idea for the [project ideas list](https://github.com/ethereum-optimism/optimism-project-ideas) - Suggest a new idea for the [project ideas list](https://github.com/ethereum-optimism/optimism-project-ideas)
- Improve the [Optimism Community Hub](https://community.optimism.io/) [documentation](https://github.com/ethereum-optimism/community-hub) or [tutorials](https://github.com/ethereum-optimism/optimism-tutorial) - Improve the [Optimism Community Hub](https://community.optimism.io/) [documentation](https://github.com/ethereum-optimism/community-hub) or [tutorials](https://github.com/ethereum-optimism/optimism-tutorial)
- Become an Optimism Ambassador, Support Nerd, and more in the [Optimism Discord](https://discord-gateway.optimism.io/) - Become an Optimism Ambassador, Support Nerd, and more in the [Optimism Discord](https://discord.gg/optimism)
...@@ -202,11 +202,18 @@ type SyncClient struct { ...@@ -202,11 +202,18 @@ type SyncClient struct {
results chan syncResult results chan syncResult
receivePayload receivePayloadFn
// resource context: all peers and mainLoop tasks inherit this, and start shutting down once resCancel() is called.
resCtx context.Context resCtx context.Context
resCancel context.CancelFunc resCancel context.CancelFunc
receivePayload receivePayloadFn // wait group: wait for the resources to close. Adding to this is only safe if the peersLock is held.
wg sync.WaitGroup wg sync.WaitGroup
// Don't allow anything to be added to the wait-group while, or after, we are shutting down.
// This is protected by peersLock.
closingPeers bool
} }
func NewSyncClient(log log.Logger, cfg *rollup.Config, newStream newStreamFn, rcv receivePayloadFn, metrics SyncClientMetrics) *SyncClient { func NewSyncClient(log log.Logger, cfg *rollup.Config, newStream newStreamFn, rcv receivePayloadFn, metrics SyncClientMetrics) *SyncClient {
...@@ -239,7 +246,9 @@ func NewSyncClient(log log.Logger, cfg *rollup.Config, newStream newStreamFn, rc ...@@ -239,7 +246,9 @@ func NewSyncClient(log log.Logger, cfg *rollup.Config, newStream newStreamFn, rc
} }
func (s *SyncClient) Start() { func (s *SyncClient) Start() {
s.peersLock.Lock()
s.wg.Add(1) s.wg.Add(1)
s.peersLock.Unlock()
go s.mainLoop() go s.mainLoop()
} }
...@@ -250,6 +259,9 @@ func (s *SyncClient) AddPeer(id peer.ID) { ...@@ -250,6 +259,9 @@ func (s *SyncClient) AddPeer(id peer.ID) {
s.log.Warn("cannot register peer for sync duties, peer was already registered", "peer", id) s.log.Warn("cannot register peer for sync duties, peer was already registered", "peer", id)
return return
} }
if s.closingPeers {
return
}
s.wg.Add(1) s.wg.Add(1)
// add new peer routine // add new peer routine
ctx, cancel := context.WithCancel(s.resCtx) ctx, cancel := context.WithCancel(s.resCtx)
...@@ -269,7 +281,12 @@ func (s *SyncClient) RemovePeer(id peer.ID) { ...@@ -269,7 +281,12 @@ func (s *SyncClient) RemovePeer(id peer.ID) {
delete(s.peers, id) delete(s.peers, id)
} }
// Close will shut down the sync client and all attached work, and block until shutdown is complete.
// This will block if the Start() has not created the main background loop.
func (s *SyncClient) Close() error { func (s *SyncClient) Close() error {
s.peersLock.Lock()
s.closingPeers = true
s.peersLock.Unlock()
s.resCancel() s.resCancel()
s.wg.Wait() s.wg.Wait()
return nil return nil
......
...@@ -2,10 +2,8 @@ package p2p ...@@ -2,10 +2,8 @@ package p2p
import ( import (
"context" "context"
"math"
"math/big" "math/big"
"testing" "testing"
"time"
"github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/network"
...@@ -13,13 +11,14 @@ import ( ...@@ -13,13 +11,14 @@ import (
mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/metrics" "github.com/ethereum-optimism/optimism/op-node/metrics"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
) )
type mockPayloadFn func(n uint64) (*eth.ExecutionPayload, error) type mockPayloadFn func(n uint64) (*eth.ExecutionPayload, error)
...@@ -120,18 +119,12 @@ func TestSinglePeerSync(t *testing.T) { ...@@ -120,18 +119,12 @@ func TestSinglePeerSync(t *testing.T) {
require.NoError(t, cl.RequestL2Range(ctx, l2Ref(10), l2Ref(20))) require.NoError(t, cl.RequestL2Range(ctx, l2Ref(10), l2Ref(20)))
// and wait for the sync results to come in (in reverse order) // and wait for the sync results to come in (in reverse order)
receiveCtx, receiveCancel := context.WithTimeout(ctx, time.Second*5)
defer receiveCancel()
for i := uint64(19); i > 10; i-- { for i := uint64(19); i > 10; i-- {
select { p := <-received
case p := <-received: require.Equal(t, uint64(p.BlockNumber), i, "expecting payloads in order")
require.Equal(t, uint64(p.BlockNumber), i, "expecting payloads in order") exp, ok := payloads[uint64(p.BlockNumber)]
exp, ok := payloads[uint64(p.BlockNumber)] require.True(t, ok, "expecting known payload")
require.True(t, ok, "expecting known payload") require.Equal(t, exp.BlockHash, p.BlockHash, "expecting the correct payload")
require.Equal(t, exp.BlockHash, p.BlockHash, "expecting the correct payload")
case <-receiveCtx.Done():
t.Fatal("did not receive all expected payloads within expected time")
}
} }
} }
...@@ -202,34 +195,20 @@ func TestMultiPeerSync(t *testing.T) { ...@@ -202,34 +195,20 @@ func TestMultiPeerSync(t *testing.T) {
// With such large range to request we are going to hit the rate-limits of B and C, // With such large range to request we are going to hit the rate-limits of B and C,
// but that means we'll balance the work between the peers. // but that means we'll balance the work between the peers.
p := <-recvA
// wait for the results to come in, based on the expected rate limit, divided by 2 (because we have 2 servers), with a buffer of 2 seconds exp, ok := payloads[uint64(p.BlockNumber)]
receiveCtx, receiveCancel := context.WithTimeout(ctx, time.Second*time.Duration(math.Ceil(float64((89-10)/peerServerBlocksRateLimit)))/2+time.Second*2) require.True(t, ok, "expecting known payload")
defer receiveCancel() require.Equal(t, exp.BlockHash, p.BlockHash, "expecting the correct payload")
for i := uint64(89); i > 10; i-- {
select {
case p := <-recvA:
exp, ok := payloads[uint64(p.BlockNumber)]
require.True(t, ok, "expecting known payload")
require.Equal(t, exp.BlockHash, p.BlockHash, "expecting the correct payload")
case <-receiveCtx.Done():
t.Fatal("did not receive all expected payloads within expected time")
}
}
// now see if B can sync a range, and fill the gap with a re-request // now see if B can sync a range, and fill the gap with a re-request
bl25 := payloads[25] // temporarily remove it from the available payloads. This will create a gap bl25 := payloads[25] // temporarily remove it from the available payloads. This will create a gap
delete(payloads, uint64(25)) delete(payloads, uint64(25))
require.NoError(t, clB.RequestL2Range(ctx, l2Ref(20), l2Ref(30))) require.NoError(t, clB.RequestL2Range(ctx, l2Ref(20), l2Ref(30)))
for i := uint64(29); i > 25; i-- { for i := uint64(29); i > 25; i-- {
select { p := <-recvB
case p := <-recvB: exp, ok := payloads[uint64(p.BlockNumber)]
exp, ok := payloads[uint64(p.BlockNumber)] require.True(t, ok, "expecting known payload")
require.True(t, ok, "expecting known payload") require.Equal(t, exp.BlockHash, p.BlockHash, "expecting the correct payload")
require.Equal(t, exp.BlockHash, p.BlockHash, "expecting the correct payload")
case <-receiveCtx.Done():
t.Fatal("did not receive all expected payloads within expected time")
}
} }
// the request for 25 should fail. See: // the request for 25 should fail. See:
// server: WARN peer requested unknown block by number num=25 // server: WARN peer requested unknown block by number num=25
...@@ -239,16 +218,11 @@ func TestMultiPeerSync(t *testing.T) { ...@@ -239,16 +218,11 @@ func TestMultiPeerSync(t *testing.T) {
payloads[25] = bl25 payloads[25] = bl25
// And request a range again, 25 is there now, and 21-24 should follow quickly (some may already have been fetched and wait in quarantine) // And request a range again, 25 is there now, and 21-24 should follow quickly (some may already have been fetched and wait in quarantine)
require.NoError(t, clB.RequestL2Range(ctx, l2Ref(20), l2Ref(26))) require.NoError(t, clB.RequestL2Range(ctx, l2Ref(20), l2Ref(26)))
receiveCtx, receiveCancel = context.WithTimeout(ctx, time.Second*10)
defer receiveCancel()
for i := uint64(25); i > 20; i-- { for i := uint64(25); i > 20; i-- {
select { p := <-recvB
case p := <-recvB: exp, ok := payloads[uint64(p.BlockNumber)]
exp, ok := payloads[uint64(p.BlockNumber)] require.True(t, ok, "expecting known payload")
require.True(t, ok, "expecting known payload") require.Equal(t, exp.BlockHash, p.BlockHash, "expecting the correct payload")
require.Equal(t, exp.BlockHash, p.BlockHash, "expecting the correct payload")
case <-receiveCtx.Done():
t.Fatal("did not receive all expected payloads within expected time")
}
} }
} }
...@@ -63,7 +63,7 @@ Also some more hooks exported by the cli but these are likely the only ones you ...@@ -63,7 +63,7 @@ Also some more hooks exported by the cli but these are likely the only ones you
Please see our [contributing.md](https://github.com/ethereum-optimism/optimism/blob/develop/CONTRIBUTING.md). No contribution is too small. Please see our [contributing.md](https://github.com/ethereum-optimism/optimism/blob/develop/CONTRIBUTING.md). No contribution is too small.
Having your contribution denied feels bad. Having your contribution denied feels bad.
Please consider [opening an issue](https://github.com/ethereum-optimism/optimism/issues) before adding any new features or apis. Please consider [opening an issue](https://github.com/ethereum-optimism/optimism/issues) before adding any new features or apis.
...@@ -73,5 +73,5 @@ If you have any problems, these resources could help you: ...@@ -73,5 +73,5 @@ If you have any problems, these resources could help you:
- [sdk documentation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/atst/docs/sdk.md) - [sdk documentation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/atst/docs/sdk.md)
- [cli documentation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/atst/docs/cli.md) - [cli documentation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/atst/docs/cli.md)
- [Optimism Discord](https://discord-gateway.optimism.io/) - [Optimism Discord](https://discord.gg/optimism)
- [Telegram group](https://t.me/+zwpJ8Ohqgl8yNjNh) - [Telegram group](https://t.me/+zwpJ8Ohqgl8yNjNh)
...@@ -84,7 +84,7 @@ contract Proxy { ...@@ -84,7 +84,7 @@ contract Proxy {
* *
* @param _implementation Address of the implementation contract. * @param _implementation Address of the implementation contract.
*/ */
function upgradeTo(address _implementation) external proxyCallIfNotAdmin { function upgradeTo(address _implementation) public virtual proxyCallIfNotAdmin {
_setImplementation(_implementation); _setImplementation(_implementation);
} }
...@@ -96,8 +96,9 @@ contract Proxy { ...@@ -96,8 +96,9 @@ contract Proxy {
* @param _data Calldata to delegatecall the new implementation with. * @param _data Calldata to delegatecall the new implementation with.
*/ */
function upgradeToAndCall(address _implementation, bytes calldata _data) function upgradeToAndCall(address _implementation, bytes calldata _data)
external public
payable payable
virtual
proxyCallIfNotAdmin proxyCallIfNotAdmin
returns (bytes memory) returns (bytes memory)
{ {
...@@ -112,7 +113,7 @@ contract Proxy { ...@@ -112,7 +113,7 @@ contract Proxy {
* *
* @param _admin New owner of the proxy contract. * @param _admin New owner of the proxy contract.
*/ */
function changeAdmin(address _admin) external proxyCallIfNotAdmin { function changeAdmin(address _admin) public virtual proxyCallIfNotAdmin {
_changeAdmin(_admin); _changeAdmin(_admin);
} }
...@@ -121,7 +122,7 @@ contract Proxy { ...@@ -121,7 +122,7 @@ contract Proxy {
* *
* @return Owner address. * @return Owner address.
*/ */
function admin() external proxyCallIfNotAdmin returns (address) { function admin() public virtual proxyCallIfNotAdmin returns (address) {
return _getAdmin(); return _getAdmin();
} }
...@@ -130,7 +131,7 @@ contract Proxy { ...@@ -130,7 +131,7 @@ contract Proxy {
* *
* @return Implementation address. * @return Implementation address.
*/ */
function implementation() external proxyCallIfNotAdmin returns (address) { function implementation() public virtual proxyCallIfNotAdmin returns (address) {
return _getImplementation(); return _getImplementation();
} }
......
...@@ -3,7 +3,11 @@ ...@@ -3,7 +3,11 @@
rm -rf artifacts forge-artifacts rm -rf artifacts forge-artifacts
# See slither.config.json for slither settings # See slither.config.json for slither settings
if [ -n "$TRIAGE_MODE" ]; then if [[ -z "$TRIAGE_MODE" ]]; then
echo "Running slither"
slither .
else
echo "Running slither in triage mode"
# Slither's triage mode will run an 'interview' in the terminal, allowing you to review each of # Slither's triage mode will run an 'interview' in the terminal, allowing you to review each of
# its findings, and specify which should be ignored in future runs of slither. This will update # its findings, and specify which should be ignored in future runs of slither. This will update
# (or create) the slither.db.json file. This DB is a cleaner alternative to adding slither-disable # (or create) the slither.db.json file. This DB is a cleaner alternative to adding slither-disable
...@@ -20,6 +24,4 @@ if [ -n "$TRIAGE_MODE" ]; then ...@@ -20,6 +24,4 @@ if [ -n "$TRIAGE_MODE" ]; then
mv $DB $TEMP_DB mv $DB $TEMP_DB
jq 'walk(if type == "object" then del(.filename_absolute) else . end)' $TEMP_DB > $DB jq 'walk(if type == "object" then del(.filename_absolute) else . end)' $TEMP_DB > $DB
rm -f $TEMP_DB rm -f $TEMP_DB
else
slither .
fi fi
{ {
"detectors_to_exclude": "assembly-usage,block-timestamp,naming-convention,solc-version", "detectors_to_exclude": "assembly-usage,block-timestamp,naming-convention,solc-version,low-level-calls",
"exclude_informational": true, "exclude_informational": true,
"exclude_low": true, "exclude_low": true,
"exclude_medium": true, "exclude_medium": true,
......
...@@ -6,6 +6,8 @@ const config: DeployConfig = { ...@@ -6,6 +6,8 @@ const config: DeployConfig = {
optimistName: '', optimistName: '',
optimistSymbol: '', optimistSymbol: '',
attestorAddress: '', attestorAddress: '',
optimistInviterName: '',
optimistInviterInviteGranter: '',
} }
export default config export default config
...@@ -6,6 +6,8 @@ const config: DeployConfig = { ...@@ -6,6 +6,8 @@ const config: DeployConfig = {
optimistName: 'Optimist', optimistName: 'Optimist',
optimistSymbol: 'OPTIMIST', optimistSymbol: 'OPTIMIST',
attestorAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3', attestorAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistInviterInviteGranter: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistInviterName: 'OptimistInviter',
} }
export default config export default config
...@@ -6,6 +6,8 @@ const config: DeployConfig = { ...@@ -6,6 +6,8 @@ const config: DeployConfig = {
optimistName: 'OP Citizenship', optimistName: 'OP Citizenship',
optimistSymbol: 'OPNFT', optimistSymbol: 'OPNFT',
attestorAddress: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', attestorAddress: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
optimistInviterInviteGranter: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
optimistInviterName: 'OptimistInviter',
} }
export default config export default config
...@@ -6,6 +6,8 @@ const config: DeployConfig = { ...@@ -6,6 +6,8 @@ const config: DeployConfig = {
optimistName: 'Optimist', optimistName: 'Optimist',
optimistSymbol: 'OPTIMIST', optimistSymbol: 'OPTIMIST',
attestorAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3', attestorAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistInviterInviteGranter: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistInviterName: 'OptimistInviter',
} }
export default config export default config
...@@ -6,6 +6,8 @@ const config: DeployConfig = { ...@@ -6,6 +6,8 @@ const config: DeployConfig = {
optimistName: 'Optimist', optimistName: 'Optimist',
optimistSymbol: 'OPTIMIST', optimistSymbol: 'OPTIMIST',
attestorAddress: '0x8F0EBDaA1cF7106bE861753B0f9F5c0250fE0819', attestorAddress: '0x8F0EBDaA1cF7106bE861753B0f9F5c0250fE0819',
optimistInviterInviteGranter: '0x8F0EBDaA1cF7106bE861753B0f9F5c0250fE0819',
optimistInviterName: 'OptimistInviter',
} }
export default config export default config
...@@ -6,6 +6,8 @@ const config: DeployConfig = { ...@@ -6,6 +6,8 @@ const config: DeployConfig = {
optimistName: 'Optimist', optimistName: 'Optimist',
optimistSymbol: 'OPTIMIST', optimistSymbol: 'OPTIMIST',
attestorAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3', attestorAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistInviterInviteGranter: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistInviterName: 'OptimistInviter',
} }
export default config export default config
//SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
/* Testing utilities */
import { Test } from "forge-std/Test.sol";
import { AttestationStation } from "../universal/op-nft/AttestationStation.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 { OptimistConstants } from "../universal/op-nft/libraries/OptimistConstants.sol";
contract OptimistAllowlist_Initializer is Test {
event AttestationCreated(
address indexed creator,
address indexed about,
bytes32 indexed key,
bytes val
);
address internal alice_allowlistAttestor;
address internal sally_coinbaseQuestAttestor;
address internal ted;
uint256 internal bobPrivateKey;
address internal bob;
AttestationStation attestationStation;
OptimistAllowlist optimistAllowlist;
OptimistInviter optimistInviter;
// Helps with EIP-712 signature generation
OptimistInviterHelper optimistInviterHelper;
function setUp() public {
alice_allowlistAttestor = makeAddr("alice_allowlistAttestor");
sally_coinbaseQuestAttestor = makeAddr("sally_coinbaseQuestAttestor");
ted = makeAddr("ted");
bobPrivateKey = 0xB0B0B0B0;
bob = vm.addr(bobPrivateKey);
vm.label(bob, "bob");
// Give alice and bob and sally some ETH
vm.deal(alice_allowlistAttestor, 1 ether);
vm.deal(sally_coinbaseQuestAttestor, 1 ether);
vm.deal(bob, 1 ether);
vm.deal(ted, 1 ether);
_initializeContracts();
}
function attestAllowlist(address _about) internal {
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: optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY(),
val: bytes("true")
});
vm.prank(alice_allowlistAttestor);
attestationStation.attest(attestationData);
}
function attestCoinbaseQuest(address _about) internal {
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: optimistAllowlist.COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY(),
val: bytes("true")
});
vm.prank(sally_coinbaseQuestAttestor);
attestationStation.attest(attestationData);
}
function inviteAndClaim(address claimer) internal {
address[] memory addresses = new address[](1);
addresses[0] = bob;
vm.prank(alice_allowlistAttestor);
// grant invites to Bob;
optimistInviter.setInviteCounts(addresses, 3);
// issue a new invite
OptimistInviter.ClaimableInvite memory claimableInvite = optimistInviterHelper
.getClaimableInviteWithNewNonce(bob);
// EIP-712 sign with Bob's private key
bytes memory signature = _getSignature(
bobPrivateKey,
optimistInviterHelper.getDigest(claimableInvite)
);
bytes32 hashedCommit = keccak256(abi.encode(claimer, signature));
// commit the invite
vm.prank(claimer);
optimistInviter.commitInvite(hashedCommit);
// wait minimum commitment period
vm.warp(optimistInviter.MIN_COMMITMENT_PERIOD() + block.timestamp);
// reveal and claim the invite
optimistInviter.claimInvite(claimer, claimableInvite, signature);
}
/**
* @notice Get signature as a bytes blob, since SignatureChecker takes arbitrary signature blobs.
*
*/
function _getSignature(uint256 _signingPrivateKey, bytes32 _digest)
internal
pure
returns (bytes memory)
{
(uint8 v, bytes32 r, bytes32 s) = vm.sign(_signingPrivateKey, _digest);
bytes memory signature = abi.encodePacked(r, s, v);
return signature;
}
function _initializeContracts() internal {
attestationStation = new AttestationStation();
optimistInviter = new OptimistInviter(alice_allowlistAttestor, attestationStation);
optimistInviter.initialize("OptimistInviter");
optimistAllowlist = new OptimistAllowlist(
attestationStation,
alice_allowlistAttestor,
sally_coinbaseQuestAttestor,
address(optimistInviter)
);
optimistInviterHelper = new OptimistInviterHelper(optimistInviter, "OptimistInviter");
}
}
contract OptimistAllowlistTest is OptimistAllowlist_Initializer {
function test_constructor_succeeds() external {
// expect attestationStation to be set
assertEq(address(optimistAllowlist.ATTESTATION_STATION()), address(attestationStation));
assertEq(optimistAllowlist.ALLOWLIST_ATTESTOR(), alice_allowlistAttestor);
assertEq(optimistAllowlist.COINBASE_QUEST_ATTESTOR(), sally_coinbaseQuestAttestor);
assertEq(address(optimistAllowlist.OPTIMIST_INVITER()), address(optimistInviter));
assertEq(optimistAllowlist.version(), "1.0.0");
}
/**
* @notice Base case, a account without any relevant attestations should not be able to mint.
*/
function test_isAllowedToMint_withoutAnyAttestations_fails() external {
assertFalse(optimistAllowlist.isAllowedToMint(bob));
}
/**
* @notice After receiving a valid allowlist attestation, the account should be able to mint.
*/
function test_isAllowedToMint_fromAllowlistAttestor_succeeds() external {
attestAllowlist(bob);
assertTrue(optimistAllowlist.isAllowedToMint(bob));
}
/**
* @notice After receiving a valid attestation from the Coinbase Quest attestor,
* the account should be able to mint.
*/
function test_isAllowedToMint_fromCoinbaseQuestAttestor_succeeds() external {
attestCoinbaseQuest(bob);
assertTrue(optimistAllowlist.isAllowedToMint(bob));
}
/**
* @notice Account that received an attestation from the OptimistInviter contract by going
* through the claim invite flow should be able to mint.
*/
function test_isAllowedToMint_fromInvite_succeeds() external {
inviteAndClaim(bob);
assertTrue(optimistAllowlist.isAllowedToMint(bob));
}
/**
* @notice Attestation from the wrong allowlist attestor should not allow minting.
*/
function test_isAllowedToMint_fromWrongAllowlistAttestor_fails() external {
// Ted is not the allowlist attestor
vm.prank(ted);
attestationStation.attest(
bob,
optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY(),
bytes("true")
);
assertFalse(optimistAllowlist.isAllowedToMint(bob));
}
/**
* @notice Coinbase quest attestation from wrong attestor should not allow minting.
*/
function test_isAllowedToMint_fromWrongCoinbaseQuestAttestor_fails() external {
// Ted is not the coinbase quest attestor
vm.prank(ted);
attestationStation.attest(
bob,
optimistAllowlist.COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY(),
bytes("true")
);
assertFalse(optimistAllowlist.isAllowedToMint(bob));
}
/**
* @notice Claiming an invite on the non-official OptimistInviter contract should not allow
* minting.
*/
function test_isAllowedToMint_fromWrongOptimistInviter_fails() external {
vm.prank(ted);
attestationStation.attest(
bob,
OptimistConstants.OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY,
bytes("true")
);
assertFalse(optimistAllowlist.isAllowedToMint(bob));
}
/**
* @notice Having multiple signals, even if one is invalid, should still allow minting.
*/
function test_isAllowedToMint_withMultipleAttestations_succeeds() external {
attestAllowlist(bob);
attestCoinbaseQuest(bob);
inviteAndClaim(bob);
assertTrue(optimistAllowlist.isAllowedToMint(bob));
// A invalid attestation, as Ted is not allowlist attestor
vm.prank(ted);
attestationStation.attest(
bob,
optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY(),
bytes("true")
);
// Since Bob has at least one valid attestation, he should be allowed to mint
assertTrue(optimistAllowlist.isAllowedToMint(bob));
}
/**
* @notice Having falsy attestation value should not allow minting.
*/
function test_isAllowedToMint_fromAllowlistAttestorWithFalsyValue_fails() external {
// First sends correct attestation
attestAllowlist(bob);
bytes32 key = optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY();
vm.expectEmit(true, true, true, false);
emit AttestationCreated(alice_allowlistAttestor, bob, key, bytes("dsafsds"));
// Invalidates existing attestation
vm.prank(alice_allowlistAttestor);
attestationStation.attest(bob, key, bytes(""));
assertFalse(optimistAllowlist.isAllowedToMint(bob));
}
/**
* @notice Having falsy attestation value from Coinbase attestor should not allow minting.
*/
function test_isAllowedToMint_fromCoinbaseQuestAttestorWithFalsyValue_fails() external {
// First sends correct attestation
attestAllowlist(bob);
bytes32 key = optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY();
vm.expectEmit(true, true, true, true);
emit AttestationCreated(alice_allowlistAttestor, bob, key, bytes(""));
// Invalidates existing attestation
vm.prank(alice_allowlistAttestor);
attestationStation.attest(bob, key, bytes(""));
assertFalse(optimistAllowlist.isAllowedToMint(bob));
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Semver } from "@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol";
import { AttestationStation } from "./AttestationStation.sol";
import { OptimistConstants } from "./libraries/OptimistConstants.sol";
/**
* @title OptimistAllowlist
* @notice Source of truth for whether an address is able to mint an Optimist NFT.
isAllowedToMint function checks various signals to return boolean value for whether an
address is eligible or not.
*/
contract OptimistAllowlist is Semver {
/**
* @notice Attestation key used by the AllowlistAttestor to manually add addresses to the
* allowlist.
*/
bytes32 public constant OPTIMIST_CAN_MINT_ATTESTATION_KEY = bytes32("optimist.can-mint");
/**
* @notice Attestation key used by Coinbase to issue attestations for Quest participants.
*/
bytes32 public constant COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY =
bytes32("coinbase.quest-eligible");
/**
* @notice Address of the AttestationStation contract.
*/
AttestationStation public immutable ATTESTATION_STATION;
/**
* @notice Attestor that issues 'optimist.can-mint' attestations.
*/
address public immutable ALLOWLIST_ATTESTOR;
/**
* @notice Attestor that issues 'coinbase.quest-eligible' attestations.
*/
address public immutable COINBASE_QUEST_ATTESTOR;
/**
* @notice Address of OptimistInviter contract that issues 'optimist.can-mint-from-invite'
* attestations.
*/
address public immutable OPTIMIST_INVITER;
/**
* @custom:semver 1.0.0
*
* @param _attestationStation Address of the AttestationStation contract.
* @param _allowlistAttestor Address of the allowlist attestor.
* @param _coinbaseQuestAttestor Address of the Coinbase Quest attestor.
* @param _optimistInviter Address of the OptimistInviter contract.
*/
constructor(
AttestationStation _attestationStation,
address _allowlistAttestor,
address _coinbaseQuestAttestor,
address _optimistInviter
) Semver(1, 0, 0) {
ATTESTATION_STATION = _attestationStation;
ALLOWLIST_ATTESTOR = _allowlistAttestor;
COINBASE_QUEST_ATTESTOR = _coinbaseQuestAttestor;
OPTIMIST_INVITER = _optimistInviter;
}
/**
* @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.
*
* Currently, address is allowed to mint if it satisfies any of the following:
* 1) Has a valid 'optimist.can-mint' attestation from the allowlist attestor.
* 2) Has a valid 'coinbase.quest-eligible' attestation from Coinbase Quest attestor
* 3) Has a valid 'optimist.can-mint-from-invite' attestation from the OptimistInviter
* contract.
*
* @param _claimer Address to check.
*
* @return Whether or not the address is allowed to mint yet.
*/
function isAllowedToMint(address _claimer) public view returns (bool) {
return
_hasAttestationFromAllowlistAttestor(_claimer) ||
_hasAttestationFromCoinbaseQuestAttestor(_claimer) ||
_hasAttestationFromOptimistInviter(_claimer);
}
/**
* @notice Checks whether an address has a valid 'optimist.can-mint' attestation from the
* allowlist attestor.
*
* @param _claimer Address to check.
*
* @return Whether or not the address has a valid attestation.
*/
function _hasAttestationFromAllowlistAttestor(address _claimer) internal view returns (bool) {
// Expected attestation value is bytes32("true")
return
_hasValidAttestation(ALLOWLIST_ATTESTOR, _claimer, OPTIMIST_CAN_MINT_ATTESTATION_KEY);
}
/**
* @notice Checks whether an address has a valid attestation from the Coinbase attestor.
*
* @param _claimer Address to check.
*
* @return Whether or not the address has a valid attestation.
*/
function _hasAttestationFromCoinbaseQuestAttestor(address _claimer)
internal
view
returns (bool)
{
// Expected attestation value is bytes32("true")
return
_hasValidAttestation(
COINBASE_QUEST_ATTESTOR,
_claimer,
COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY
);
}
/**
* @notice Checks whether an address has a valid attestation from the OptimistInviter contract.
*
* @param _claimer Address to check.
*
* @return Whether or not the address has a valid attestation.
*/
function _hasAttestationFromOptimistInviter(address _claimer) internal view returns (bool) {
// Expected attestation value is the inviter's address
return
_hasValidAttestation(
OPTIMIST_INVITER,
_claimer,
OptimistConstants.OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY
);
}
/**
* @notice Checks whether an address has a valid truthy attestation.
* Any attestation val other than bytes32("") is considered truthy.
*
* @param _creator Address that made the attestation.
* @param _about Address attestation is about.
* @param _key Key of the attestation.
*
* @return Whether or not the address has a valid truthy attestation.
*/
function _hasValidAttestation(
address _creator,
address _about,
bytes32 _key
) internal view returns (bool) {
return ATTESTATION_STATION.attestations(_creator, _about, _key).length > 0;
}
}
...@@ -61,9 +61,9 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { ...@@ -61,9 +61,9 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
addr addr
) )
const implementation = await Proxy.callStatic.implementation({ const implementation = await Proxy.connect(
from: ethers.constants.AddressZero, ethers.constants.AddressZero
}) ).callStatic.implementation()
console.log(`implementation is set to ${implementation}`) console.log(`implementation is set to ${implementation}`)
if ( if (
getAddress(implementation) !== getAddress(implementation) !==
...@@ -82,9 +82,9 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { ...@@ -82,9 +82,9 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
} }
const l2ProxyOwnerAddress = deployConfig.l2ProxyOwnerAddress const l2ProxyOwnerAddress = deployConfig.l2ProxyOwnerAddress
const admin = await Proxy.callStatic.admin({ const admin = await Proxy.connect(
from: ethers.constants.AddressZero, ethers.constants.AddressZero
}) ).callStatic.admin()
console.log(`admin is set to ${admin}`) console.log(`admin is set to ${admin}`)
if (getAddress(admin) !== getAddress(l2ProxyOwnerAddress)) { if (getAddress(admin) !== getAddress(l2ProxyOwnerAddress)) {
console.log('admin not set correctly') console.log('admin not set correctly')
...@@ -99,7 +99,7 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { ...@@ -99,7 +99,7 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
console.log('Contract deployment complete') console.log('Contract deployment complete')
await assertContractVariable(Proxy, 'admin', l2ProxyOwnerAddress) await assertContractVariable(Proxy, 'admin', l2ProxyOwnerAddress)
await assertContractVariable(AttestationStation, 'version', '1.0.0') await assertContractVariable(AttestationStation, 'version', '1.1.0')
} }
deployFn.tags = ['AttestationStationProxy', 'OptimistEnvironment'] deployFn.tags = ['AttestationStationProxy', 'OptimistEnvironment']
......
/* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types'
import { HardhatRuntimeEnvironment } from 'hardhat/types'
import '@nomiclabs/hardhat-ethers'
import '@eth-optimism/hardhat-deploy-config'
import 'hardhat-deploy'
import type { DeployConfig } from '../../src'
const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
const deployConfig = hre.deployConfig as DeployConfig
const { deployer } = await hre.getNamedAccounts()
console.log(`Deploying OptimistInviter implementation with ${deployer}`)
const Deployment__AttestationStation = await hre.deployments.get(
'AttestationStationProxy'
)
const attestationStationAddress = Deployment__AttestationStation.address
console.log(`Using ${attestationStationAddress} as the ATTESTATION_STATION`)
console.log(
`Using ${deployConfig.optimistInviterInviteGranter} as INVITE_GRANTER`
)
const { deploy } = await hre.deployments.deterministic('OptimistInviter', {
salt: hre.ethers.utils.solidityKeccak256(['string'], ['OptimistInviter']),
from: deployer,
args: [
deployConfig.optimistInviterInviteGranter,
attestationStationAddress,
],
log: true,
})
await deploy()
}
deployFn.tags = ['OptimistInviter', 'OptimistEnvironment']
deployFn.dependencies = ['AttestationStationProxy']
export default deployFn
/* Imports: External */
import assert from 'assert'
import { DeployFunction } from 'hardhat-deploy/dist/types'
import { HardhatRuntimeEnvironment } from 'hardhat/types'
import '@eth-optimism/hardhat-deploy-config'
import '@nomiclabs/hardhat-ethers'
import 'hardhat-deploy'
import { assertContractVariable } from '@eth-optimism/contracts-bedrock/src/deploy-utils'
import { ethers, utils } from 'ethers'
import type { DeployConfig } from '../../src'
const { getAddress } = utils
// Required conditions before deploying - Specified in `deployFn.dependencies`
// - AttestationStationProxy is deployed and points to the correct implementation
// - OptimistInviterImpl is deployed
//
// Steps
// 1. Deploy OptimistInviterProxy
// 2. Point the newly deployed proxy to the implementation, if it hasn't been done already
// 3. Update the admin of the proxy to the l2ProxyOwnerAddress, if it hasn't been done already
// 4. Basic sanity checks for contract variables
const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
const deployConfig = hre.deployConfig as DeployConfig
// Deployer should be set in hardhat.config.ts
const { deployer } = await hre.getNamedAccounts()
// We want the ability to deploy to a deterministic address, so we need the init bytecode to be
// consistent across deployments. The ddd will quickly transfer the ownership of the Proxy to a
// multisig after deployment.
//
// We need a consistent ddd, since the Proxy takes a `_admin` constructor argument, which
// affects the init bytecode and hence deployed address.
const ddd = deployConfig.ddd
if (getAddress(deployer) !== getAddress(ddd)) {
// Not a hard requirement. We can deploy with any account and just set the `_admin` to the
// ddd, but requiring that the deployer is the same as the ddd minimizes number of hot wallets
// we need to keep track of during deployment.
throw new Error('Must deploy with the ddd')
}
// Get the up to date deployment of the OptimistInviter contract
const Deployment__OptimistInviterImpl = await hre.deployments.get(
'OptimistInviter'
)
console.log(`Deploying OptimistInviterProxy with ${deployer}`)
// Deploys the Proxy.sol contract with the `_admin` constructor param set to the ddd (=== deployer).
const { deploy } = await hre.deployments.deterministic(
'OptimistInviterProxy',
{
salt: hre.ethers.utils.solidityKeccak256(
['string'],
['OptimistInviterProxy']
),
contract: 'Proxy',
from: deployer,
args: [deployer],
log: true,
}
)
// Deploy the Proxy contract
await deploy()
const Deployment__OptimistInviterProxy = await hre.deployments.get(
'OptimistInviterProxy'
)
console.log(
`OptimistProxy deployed to ${Deployment__OptimistInviterProxy.address}`
)
// Deployed Proxy.sol contract
const Proxy = await hre.ethers.getContractAt(
'Proxy',
Deployment__OptimistInviterProxy.address
)
// Deployed Proxy.sol contract with the OptimistInviter interface
const OptimistInviter = await hre.ethers.getContractAt(
'OptimistInviter',
Deployment__OptimistInviterProxy.address
)
// Gets the current implementation address the proxy is pointing to.
// callStatic is used since the `Proxy.implementation()` is not a view function and ethers will
// try to make a transaction if we don't use callStatic. Using the zero address as `from` lets us
// call functions on the proxy and not trigger the delegatecall. See Proxy.sol proxyCallIfNotAdmin
// modifier for more details.
const implementation = await Proxy.connect(
ethers.constants.AddressZero
).callStatic.implementation()
console.log(`implementation set to ${implementation}`)
if (
getAddress(implementation) !==
getAddress(Deployment__OptimistInviterImpl.address)
) {
// If the proxy isn't pointing to the correct implementation, we need to set it to the correct
// one, then call initialize() in the proxy's context.
console.log(
'implementation not set to OptimistInviter implementation contract'
)
console.log(
`Setting implementation to ${Deployment__OptimistInviterImpl.address}`
)
const name = deployConfig.optimistInviterName
// Create the calldata for the call to `initialize()`
const calldata = OptimistInviter.interface.encodeFunctionData(
'initialize',
[name]
)
// ethers.Signer for the ddd
const dddSigner = await hre.ethers.provider.getSigner(deployer)
// Point the proxy to the deployed OptimistInviter implementation contract,
// and call `initialize()` in the proxy's context
const tx = await Proxy.connect(dddSigner).upgradeToAndCall(
Deployment__OptimistInviterImpl.address,
calldata
)
const receipt = await tx.wait()
console.log(`implementation set in ${receipt.transactionHash}`)
} else {
console.log(
'implementation already set to OptimistInviter implementation contract'
)
}
const l2ProxyOwnerAddress = deployConfig.l2ProxyOwnerAddress
// Get the current proxy admin address
const admin = await Proxy.connect(
ethers.constants.AddressZero
).callStatic.admin()
console.log(`admin currently set to ${admin}`)
if (getAddress(admin) !== getAddress(l2ProxyOwnerAddress)) {
// If the proxy admin isn't the l2ProxyOwnerAddress, we need to update it
// We're assuming that the proxy admin is the ddd right now.
console.log('admin is not set to the l2ProxyOwnerAddress')
console.log(`Setting admin to ${l2ProxyOwnerAddress}`)
// ethers.Signer for the ddd
const dddSigner = await hre.ethers.provider.getSigner(deployer)
// change admin to the l2ProxyOwnerAddress
const tx = await Proxy.connect(dddSigner).changeAdmin(l2ProxyOwnerAddress)
const receipt = await tx.wait()
console.log(`admin set in ${receipt.transactionHash}`)
} else {
console.log('admin already set to proxy owner address')
}
const Deployment__AttestationStation = await hre.deployments.get(
'AttestationStationProxy'
)
await assert(
getAddress(
await Proxy.connect(ethers.constants.AddressZero).callStatic.admin()
) === getAddress(l2ProxyOwnerAddress)
)
await assertContractVariable(OptimistInviter, 'version', '1.0.0')
await assertContractVariable(
OptimistInviter,
'INVITE_GRANTER',
deployConfig.optimistInviterInviteGranter
)
await assertContractVariable(
OptimistInviter,
'ATTESTATION_STATION',
Deployment__AttestationStation.address
)
await assertContractVariable(OptimistInviter, 'EIP712_VERSION', '1.0.0')
}
deployFn.tags = ['OptimistInviterProxy', 'OptimistEnvironment']
deployFn.dependencies = ['AttestationStationProxy', 'OptimistInviter']
export default deployFn
...@@ -48,9 +48,9 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { ...@@ -48,9 +48,9 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
Deployment__OptimistProxy.address Deployment__OptimistProxy.address
) )
const implementation = await Proxy.callStatic.implementation({ const implementation = await Proxy.connect(
from: ethers.constants.AddressZero, ethers.constants.AddressZero
}) ).callStatic.implementation()
console.log(`implementation set to ${implementation}`) console.log(`implementation set to ${implementation}`)
if (getAddress(implementation) !== getAddress(Deployment__Optimist.address)) { if (getAddress(implementation) !== getAddress(Deployment__Optimist.address)) {
console.log('implementation not set to Optimist contract') console.log('implementation not set to Optimist contract')
...@@ -75,9 +75,9 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { ...@@ -75,9 +75,9 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
} }
const l2ProxyOwnerAddress = deployConfig.l2ProxyOwnerAddress const l2ProxyOwnerAddress = deployConfig.l2ProxyOwnerAddress
const admin = await Proxy.callStatic.admin({ const admin = await Proxy.connect(
from: ethers.constants.AddressZero, ethers.constants.AddressZero
}) ).callStatic.admin()
console.log(`admin set to ${admin}`) console.log(`admin set to ${admin}`)
if (getAddress(admin) !== getAddress(l2ProxyOwnerAddress)) { if (getAddress(admin) !== getAddress(l2ProxyOwnerAddress)) {
console.log('detected admin is not set') console.log('detected admin is not set')
...@@ -96,7 +96,7 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { ...@@ -96,7 +96,7 @@ const deployFn: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
await assertContractVariable(Proxy, 'admin', l2ProxyOwnerAddress) await assertContractVariable(Proxy, 'admin', l2ProxyOwnerAddress)
await assertContractVariable(Optimist, 'name', deployConfig.optimistName) await assertContractVariable(Optimist, 'name', deployConfig.optimistName)
await assertContractVariable(Optimist, 'verson', '1.0.0') await assertContractVariable(Optimist, 'version', '1.0.0')
await assertContractVariable(Optimist, 'symbol', deployConfig.optimistSymbol) await assertContractVariable(Optimist, 'symbol', deployConfig.optimistSymbol)
await assertContractVariable( await assertContractVariable(
Optimist, Optimist,
......
...@@ -29,10 +29,20 @@ export interface DeployConfig { ...@@ -29,10 +29,20 @@ export interface DeployConfig {
optimistSymbol: string optimistSymbol: string
/** /**
* Address of the priviledged attestor for the Optimist contract. * Address of the privileged attestor for the Optimist contract.
*/ */
attestorAddress: string attestorAddress: string
/**
* Address of the privileged account for the OptimistInviter contract that can grant invites.
*/
optimistInviterInviteGranter: string
/**
* Name of OptimistInviter contract, used for the EIP712 domain separator.
*/
optimistInviterName: string
/** /**
* Address of the owner of the proxies on L2. There will be a ProxyAdmin deployed as a predeploy * Address of the owner of the proxies on L2. There will be a ProxyAdmin deployed as a predeploy
* after bedrock, so the owner of proxies should be updated to that after the upgrade. * after bedrock, so the owner of proxies should be updated to that after the upgrade.
...@@ -63,6 +73,12 @@ export const configSpec: DeployConfigSpec<DeployConfig> = { ...@@ -63,6 +73,12 @@ export const configSpec: DeployConfigSpec<DeployConfig> = {
attestorAddress: { attestorAddress: {
type: 'address', type: 'address',
}, },
optimistInviterInviteGranter: {
type: 'address',
},
optimistInviterName: {
type: 'string',
},
l2ProxyOwnerAddress: { l2ProxyOwnerAddress: {
type: 'address', type: 'address',
}, },
......
...@@ -277,6 +277,12 @@ export class L1IngestionService extends BaseService<L1IngestionServiceOptions> { ...@@ -277,6 +277,12 @@ export class L1IngestionService extends BaseService<L1IngestionServiceOptions> {
depositTargetL1Block, depositTargetL1Block,
handleEventsTransactionEnqueued handleEventsTransactionEnqueued
) )
} else {
this.logger.info('Deposit shutoff reached', {
depositTargetL1Block,
highestSyncedL1Block,
depositShutoffBlock,
})
} }
await this._syncEvents( await this._syncEvents(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment