Skip to content

Commit 65f868e

Browse files
djviauDJViau
authored andcommitted
add some new deploy stuff
1 parent 8c12fca commit 65f868e

11 files changed

Lines changed: 280 additions & 24 deletions

File tree

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Shipyard
22

3-
Shipyard is a Forge template for smart contract development. See [the tutorial](exampleNftTutorial/Overview.md) for detailed instructions on using Shipyard or jump down to [the usage section](#usage) below for more info on how it works.
3+
Shipyard is a Forge template for smart contract development. See [the tutorial](exampleNftTutorial/README.md) for detailed instructions on using Shipyard or jump down to [the usage section](#usage) below for more info on how it works.
44

55
## Overview
66
Shipyard comes with some batteries included
@@ -23,7 +23,7 @@ Shipyard can be used as a starting point or a toolkit in a wide variety of circu
2323

2424
### Quick Deploy Guide
2525

26-
To deploy an NFT contract to the Goerli testnet, fund an address with 0.25 Goerli ETH, open a terminal window, and run the following commands:
26+
To deploy an NFT contract to the Goerli testnet, fund an address with 0.1 Goerli ETH, open a terminal window, and run the following commands:
2727

2828
Create a directory and `cd` into it:
2929
```bash
@@ -43,7 +43,7 @@ Install forge, cast, anvil, and chisel by running:
4343
foundryup
4444
```
4545

46-
Create a new Foundry project based on Shipyard, which also initializes a new git repository, all in the working directory.
46+
Create a new Foundry project based on Shipyard, which also initializes a new git repository, in the working directory.
4747
```bash
4848
forge init --template projectopensea/shipyard
4949
```
@@ -111,7 +111,7 @@ To generate reports, run
111111
- [ ] Pin to version
112112
- [x] Shipyard-core
113113
- [ ] Pin to version
114-
- [ ] Include a base cross-chain deploy script
114+
- [x] Include a base cross-chain deploy script
115115
- [ ] Figure out if there's a way we can make `forge verify-contract` more ergonomic
116116
- [ ] Top-level helpers:
117117
- [x] PRB's `reinit-submodules` script as top-level helper

exampleNftTutorial/Appendix.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
## Useful Aliases
2+
3+
```bash
4+
alias gm="foundryup"
5+
alias fb="forge build"
6+
alias fc="forge clean"
7+
alias ff="forge fmt"
8+
alias ft="forge test -vvv"
9+
10+
reinit() {
11+
git submodule deinit -f .
12+
git submodule update --init --recursive
13+
}
14+
```
15+
16+
17+
## Notes
18+
19+
- When experiencing irregular behavior that's difficult to explain, it's worth running `foundryup` and `forge clean` just to make sure that everything's all synced on the latest. See [https://book.getfoundry.sh/reference/forge/forge-clean](https://book.getfoundry.sh/reference/forge/forge-clean) for more.
20+
21+
- It's advisable to run `forge build --watch` while writing a new function to make sure the compiler is happy with your code, especially if the syntax highlighting lags. It's advisable to run `forge test -vvv --watch` while tweaking a test or tweaking code that a test targets. See [https://book.getfoundry.sh/forge/tests#watch-mode](https://book.getfoundry.sh/forge/tests#watch-mode) for more info.
22+
23+
- If you're searching for something in shipyard-core, but you don't want to see all of the files in shipyard-core's libraries, include `lib/shipyard-core` and exclude `lib/shipyard-core/lib`.

exampleNftTutorial/Deploying.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,16 @@ Once you've got a salt that produces a deploy address you're happy with, deployi
8787

8888
Since you're fluent with the Foundry toolset now, you could also use [`cast send`](https://book.getfoundry.sh/reference/cast/cast-send?highlight=cast%20send#cast-send) to send the transaction from the command line:
8989

90-
```log
90+
```bash
9191
cast send 0x0000000000FFe8B47B3e2130213B802212439497 "safeCreate2(bytes32, bytes)" 0x... 0x...
9292
```
9393

94+
Alternatively, you can adapt the [cross chain deploy script](../script/CrossChainDeploy.s.sol) to deploy.
95+
9496
Finally, verify your contract [on Etherscan](https://etherscan.io/verifyContract) or [using Forge](https://book.getfoundry.sh/forge/deploying?highlight=verify#verifying-a-pre-existing-contract).
9597

9698
To be clear, this is mostly about the cool factor. But it also gives you gas efficiency benefits, sceurity benefits, cross-chain consistency, and more. And since you know the address before you deploy, you can code it into your frontend, etc. before you've revealed it to the rest of the world.
9799

98100
## Back to the table of contents
99101

100-
[Take it from the top](Overview.md)
102+
[Take it from the top](README.md) or [view the appendix](Appendix.md), which contains addition info, such as useful aliases and links.
File renamed without changes.

script/CrossChainDeploy.s.sol

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.20;
3+
4+
import "forge-std/Script.sol";
5+
import "forge-std/console.sol";
6+
import "../src/Dockmaster.sol";
7+
8+
interface ImmutableCreate2Factory {
9+
function hasBeenDeployed(address deploymentAddress) external view returns (bool);
10+
11+
function findCreate2Address(bytes32 salt, bytes calldata initializationCode)
12+
external
13+
view
14+
returns (address deploymentAddress);
15+
16+
function safeCreate2(bytes32 salt, bytes calldata initializationCode)
17+
external
18+
payable
19+
returns (address deploymentAddress);
20+
}
21+
22+
/**
23+
* @title CrossChainDeployScript
24+
* @notice This script deploys a contract to the same address regardless of
25+
* which chain it's run on. Simulate running it by entering `forge
26+
* script script/CrossChainDeploy.s.sol --tc CrossChainDeployScript
27+
* --sender <the_caller_address> --fork-url $GOERLI_RPC_URL -vvvv` in
28+
* the terminal. To run it for real, change it to `forge script
29+
* script/CrossChainDeploy.s.sol --tc CrossChainDeployScript
30+
* --private-key $MY_ACTUAL_PK_BE_CAREFUL --fork-url $GOERLI_RPC_URL
31+
* --broadcast`. Note that it's probably a bad idea to deploy an NFT
32+
* contract to multiple chains, but this script makes more sense if it
33+
* targets an actual contract, and Dockmaster is what's available, so
34+
* ¯\_(ツ)_/¯.
35+
*
36+
*/
37+
contract CrossChainDeployScript is Script {
38+
// Define a mapping from chainID to its respective URL
39+
mapping(uint256 => string) chainToBlockExplorer;
40+
41+
// Set up the immutable create2 factory and conduit controller addresses.
42+
ImmutableCreate2Factory private constant IMMUTABLE_CREATE2_FACTORY =
43+
ImmutableCreate2Factory(0x0000000000FFe8B47B3e2130213B802212439497);
44+
45+
// Set up the default salt. To deploy with a specific salt, replace it here
46+
// or pass it in as an argument to the `deploy` function below.
47+
bytes32 private constant DEFAULT_SALT = bytes32(uint256(0x1));
48+
49+
function setUp() public {
50+
// Populate the chainToBlockExplorer mappings.
51+
_setPrefixValues();
52+
}
53+
54+
function run() public {
55+
vm.startBroadcast();
56+
57+
// Create a new Dockmaster contract.
58+
deploy(bytes.concat(type(Dockmaster).creationCode, abi.encode("Dockmaster", "DM", msg.sender)));
59+
60+
vm.stopBroadcast();
61+
}
62+
63+
function deploy(bytes memory initCode) internal returns (address) {
64+
return deploy(DEFAULT_SALT, initCode);
65+
}
66+
67+
function deploy(bytes32 salt, bytes memory initCode) internal returns (address) {
68+
bytes32 initCodeHash = keccak256(initCode);
69+
address deploymentAddress = address(
70+
uint160(
71+
uint256(keccak256(abi.encodePacked(hex"ff", address(IMMUTABLE_CREATE2_FACTORY), salt, initCodeHash)))
72+
)
73+
);
74+
bool deploying;
75+
if (!IMMUTABLE_CREATE2_FACTORY.hasBeenDeployed(deploymentAddress)) {
76+
deploymentAddress = IMMUTABLE_CREATE2_FACTORY.safeCreate2(salt, initCode);
77+
deploying = true;
78+
}
79+
80+
if (!deploying) {
81+
console.log(
82+
pad("Found", 10),
83+
pad(LibString.toHexString(deploymentAddress), 43),
84+
LibString.toHexString(uint256(initCodeHash))
85+
);
86+
} else {
87+
// The `"\x1b[1m%s\x1b[0m"` causes the string to be printed in bold.
88+
string memory boldString = "\x1b[1m%s\x1b[0m";
89+
string memory deployHypeString =
90+
_chainIsSupportedByABlockExplorer() ? "Deployed an NFT contract at:" : "Deployed an NFT contract!";
91+
92+
console.log(boldString, deployHypeString);
93+
console.log(boldString, _getBlockExplorerLog(deploymentAddress));
94+
console.log("");
95+
console.log(boldString, "Initialization code hash:", LibString.toHexString(uint256(initCodeHash)));
96+
}
97+
98+
return deploymentAddress;
99+
}
100+
101+
function pad(string memory name, uint256 n) internal pure returns (string memory) {
102+
string memory padded = name;
103+
while (bytes(padded).length < n) {
104+
padded = string.concat(padded, " ");
105+
}
106+
return padded;
107+
}
108+
109+
////////////////////////////////////////////////////////////////////////////
110+
// Log Helpers //
111+
////////////////////////////////////////////////////////////////////////////
112+
113+
function _getBlockExplorerLog(address targetContract) internal view returns (string memory) {
114+
// Only return a log if the chain is supported.
115+
string memory blockExplorerString = string(
116+
abi.encodePacked(
117+
"View the contract on a block explorer: ",
118+
chainToBlockExplorer[block.chainid],
119+
vm.toString(targetContract)
120+
)
121+
);
122+
123+
return _chainIsSupportedByABlockExplorer() ? blockExplorerString : "";
124+
}
125+
126+
function _chainIsSupportedByABlockExplorer() internal view returns (bool) {
127+
return keccak256(bytes(chainToBlockExplorer[block.chainid])) != keccak256(bytes(""));
128+
}
129+
130+
function _setPrefixValues() internal {
131+
chainToBlockExplorer[1] = "https://etherscan.io/address/";
132+
chainToBlockExplorer[5] = "https://goerli.etherscan.io/address/";
133+
chainToBlockExplorer[11155111] = "https://sepolia.etherscan.io/address/";
134+
chainToBlockExplorer[137] = "https://polygonscan.com/address/";
135+
chainToBlockExplorer[80001] = "https://mumbai.polygonscan.com/address/";
136+
chainToBlockExplorer[43114] = "https://snowtrace.io/address/";
137+
chainToBlockExplorer[43113] = "https://testnet.snowtrace.io/address/";
138+
chainToBlockExplorer[100] = "https://gnosisscan.io/address/";
139+
chainToBlockExplorer[10200] = "https://gnosis-chiado.blockscout.com/address/";
140+
chainToBlockExplorer[42161] = "https://arbiscan.io/address/";
141+
chainToBlockExplorer[421613] = "https://goerli.arbiscan.io/address/";
142+
chainToBlockExplorer[42170] = "https://nova.arbiscan.io/address/";
143+
chainToBlockExplorer[10] = "https://optimistic.etherscan.io/address/";
144+
chainToBlockExplorer[420] = "https://goerli-optimism.etherscan.io/address/";
145+
chainToBlockExplorer[8453] = "https://basescan.org/address/";
146+
chainToBlockExplorer[84531] = "https://goerli.basescan.org/address";
147+
chainToBlockExplorer[7777777] = "https://explorer.zora.energy/address/";
148+
chainToBlockExplorer[999] = "https://testnet.explorer.zora.energy/address/";
149+
}
150+
}

script/Deploy.s.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ import "../src/Dockmaster.sol";
1616
contract DeployScript is Script {
1717
function run() public {
1818
vm.broadcast();
19-
new Dockmaster("Dockmaster NFT", "DM");
19+
new Dockmaster("Dockmaster NFT", "DM", address(0));
2020
}
2121
}

script/DeployAndMint.s.sol

Lines changed: 93 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ import "../src/Dockmaster.sol";
1515
*
1616
*/
1717
contract DeployScript is Script {
18+
// Define a mapping from chainID to its respective URL
19+
mapping(uint256 => string) chainToBlockExplorer;
20+
mapping(uint256 => string) chainToOpenSea;
21+
22+
function setUp() public {
23+
// Populate the chainToBlockExplorer and chainToOpenSea mappings.
24+
_setPrefixValues();
25+
}
26+
1827
function run() public {
1928
// The deploy and the mint need to happen within the same
2029
// `startBroadcast`/`stopBroadcast` block. Otherwise, the script will
@@ -24,32 +33,103 @@ contract DeployScript is Script {
2433
vm.startBroadcast();
2534

2635
// Create a new Dockmaster contract.
27-
Dockmaster targetContract = new Dockmaster("Dockmaster NFT", "DM");
36+
Dockmaster targetContract = new Dockmaster("Dockmaster NFT", "DM", address(0));
2837

2938
// Mint a token to the caller.
3039
targetContract.mint(address(0));
3140

3241
vm.stopBroadcast();
3342

34-
// Log some links to Etherscan and OpenSea.
35-
string memory openSeaPrefix =
36-
block.chainid == 5 ? "https://testnets.opensea.io/assets/goerli/" : "https://opensea.io/assets/ethereum/";
37-
38-
string memory etherscanPrefix =
39-
block.chainid == 5 ? "https://goerli.etherscan.io/address/" : "https://etherscan.io/address/";
40-
4143
// The `"\x1b[1m%s\x1b[0m"` causes the string to be printed in bold.
42-
console.log("\x1b[1m%s\x1b[0m", "Deployed an NFT contract at:");
43-
console.log(string(abi.encodePacked(etherscanPrefix, vm.toString(address(targetContract)))));
44+
string memory boldString = "\x1b[1m%s\x1b[0m";
45+
string memory deployHypeString =
46+
_chainIsSupportedByABlockExplorer() ? "Deployed an NFT contract at:" : "Deployed an NFT contract!";
47+
string memory mintHypeString =
48+
_chainIsSupportedByOpenSea() ? "Minted a token to the caller:" : "Minted a token to the caller!";
49+
50+
console.log(boldString, deployHypeString);
51+
console.log(boldString, _getBlockExplorerLog(address(targetContract)));
4452
console.log("");
45-
console.log("\x1b[1m%s\x1b[0m", "Minted a token to the caller:");
46-
console.log(string(abi.encodePacked(openSeaPrefix, vm.toString(address(targetContract)), "/1")));
53+
console.log(boldString, mintHypeString);
54+
console.log(boldString, _getOpenSeaLog(address(targetContract)));
4755
console.log("");
4856
console.log(
49-
"\x1b[1m%s\x1b[0m",
57+
boldString,
5058
"Please note that it will take perhaps 15 seconds "
5159
"for the transaction to be mined and a few additional seconds for "
5260
"OpenSea to pick up the transaction and reflect the existence of the NFT."
5361
);
5462
}
63+
64+
function _getBlockExplorerLog(address targetContract) internal view returns (string memory) {
65+
// Only return a log if the chain is supported.
66+
string memory blockExplorerString = string(
67+
abi.encodePacked(
68+
"View the contract on a block explorer: ",
69+
chainToBlockExplorer[block.chainid],
70+
vm.toString(targetContract)
71+
)
72+
);
73+
74+
return _chainIsSupportedByABlockExplorer() ? blockExplorerString : "";
75+
}
76+
77+
function _getOpenSeaLog(address targetContract) internal view returns (string memory) {
78+
// Only return a log if the chain is supported.
79+
string memory openSeaString = string(
80+
abi.encodePacked(
81+
"View the token on OpenSea: ", chainToOpenSea[block.chainid], vm.toString(targetContract), "/1"
82+
)
83+
);
84+
85+
return _chainIsSupportedByOpenSea() ? openSeaString : "";
86+
}
87+
88+
function _chainIsSupportedByABlockExplorer() internal view returns (bool) {
89+
return keccak256(bytes(chainToBlockExplorer[block.chainid])) != keccak256(bytes(""));
90+
}
91+
92+
function _chainIsSupportedByOpenSea() internal view returns (bool) {
93+
return keccak256(bytes(chainToOpenSea[block.chainid])) != keccak256(bytes(""));
94+
}
95+
96+
function _setPrefixValues() internal {
97+
chainToBlockExplorer[1] = "https://etherscan.io/address/";
98+
chainToBlockExplorer[5] = "https://goerli.etherscan.io/address/";
99+
chainToBlockExplorer[11155111] = "https://sepolia.etherscan.io/address/";
100+
chainToBlockExplorer[137] = "https://polygonscan.com/address/";
101+
chainToBlockExplorer[80001] = "https://mumbai.polygonscan.com/address/";
102+
chainToBlockExplorer[43114] = "https://snowtrace.io/address/";
103+
chainToBlockExplorer[43113] = "https://testnet.snowtrace.io/address/";
104+
chainToBlockExplorer[100] = "https://gnosisscan.io/address/";
105+
chainToBlockExplorer[10200] = "https://gnosis-chiado.blockscout.com/address/";
106+
chainToBlockExplorer[42161] = "https://arbiscan.io/address/";
107+
chainToBlockExplorer[421613] = "https://goerli.arbiscan.io/address/";
108+
chainToBlockExplorer[42170] = "https://nova.arbiscan.io/address/";
109+
chainToBlockExplorer[10] = "https://optimistic.etherscan.io/address/";
110+
chainToBlockExplorer[420] = "https://goerli-optimism.etherscan.io/address/";
111+
chainToBlockExplorer[8453] = "https://basescan.org/address/";
112+
chainToBlockExplorer[84531] = "https://goerli.basescan.org/address";
113+
chainToBlockExplorer[7777777] = "https://explorer.zora.energy/address/";
114+
chainToBlockExplorer[999] = "https://testnet.explorer.zora.energy/address/";
115+
116+
chainToOpenSea[1] = "https://opensea.io/assets/ethereum/";
117+
chainToOpenSea[5] = "https://testnets.opensea.io/assets/goerli/";
118+
chainToOpenSea[11155111] = "https://testnets.opensea.io/assets/sepolia/";
119+
chainToOpenSea[137] = "https://opensea.io/assets/matic/";
120+
chainToOpenSea[80001] = "https://testnets.opensea.io/assets/mumbai/";
121+
chainToOpenSea[43114] = "https://opensea.io/assets/avalanche/";
122+
chainToOpenSea[43113] = "https://testnets.opensea.io/assets/fuji/";
123+
chainToOpenSea[100] = "https://opensea.io/assets/gnosis/";
124+
chainToOpenSea[10200] = "https://testnets.opensea.io/assets/gnosis-chiado/";
125+
chainToOpenSea[42161] = "https://opensea.io/assets/arbitrum/";
126+
chainToOpenSea[421613] = "https://testnets.opensea.io/assets/arbitrum-goerli/";
127+
chainToOpenSea[42170] = "https://opensea.io/assets/arbitrum-nova/";
128+
chainToOpenSea[10] = "https://opensea.io/assets/optimism/";
129+
chainToOpenSea[420] = "https://testnets.opensea.io/assets/optimism-goerli/";
130+
chainToOpenSea[8453] = "https://opensea.io/assets/base/";
131+
chainToOpenSea[84531] = "https://testnets.opensea.io/assets/base-goerli/";
132+
chainToOpenSea[7777777] = "https://opensea.io/assets/zora/";
133+
chainToOpenSea[999] = "https://testnets.opensea.io/assets/zora-testnet/";
134+
}
55135
}

src/Dockmaster.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ contract Dockmaster is AbstractNFT {
2727

2828
error UnauthorizedMinter();
2929

30-
constructor(string memory __name, string memory __symbol) AbstractNFT(__name, __symbol) {
30+
constructor(string memory __name, string memory __symbol, address __owner) AbstractNFT(__name, __symbol) {
3131
_name = __name;
3232
_symbol = __symbol;
33+
_initializeOwner(__owner == address(0) ? msg.sender : __owner);
3334
emit Hail(string(abi.encodePacked("Hi Mom! I'm deploying my very own ", __name, " contract!")));
3435
}
3536

test-ffi/Dockmaster.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ contract DockmasterTest is Test {
2727
string PROCESS_JSON_PATH = "./test-ffi/scripts/process_json.js";
2828

2929
function setUp() public {
30-
dockmaster = new Dockmaster('Dockmaster', 'DM');
30+
dockmaster = new Dockmaster('Dockmaster', 'DM', address(0));
3131
for (uint256 i; i < 10; i++) {
3232
vm.prank(address(0));
3333
dockmaster.mint(address(this));

0 commit comments

Comments
 (0)