Skip to content

Commit

Permalink
eaning up docs
Browse files Browse the repository at this point in the history
  • Loading branch information
djviau committed Sep 21, 2023
1 parent 718c18e commit cd79ff0
Show file tree
Hide file tree
Showing 12 changed files with 446 additions and 25 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@
[submodule "lib/seaport-types"]
path = lib/seaport-types
url = https://github.com/projectopensea/seaport-types
[submodule "lib/shipyard-core"]
path = lib/shipyard-core
url = https://github.com/projectopensea/shipyard-core
43 changes: 23 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,34 +41,34 @@ Shipyard comes with some batteries included

Shipyard can be used as a starting point or a toolkit in a wide variety of circumstances. In general, if you're building something NFT related, you're likely to find something useful here. For the sake of exploring some of what Shipyard has to offer in concrete terms, here's a guide on how to deploy an NFT contract.

### Quick Start Guide
### Quick Deploy Guide

To deploy an NFT contract to the Goerli testnet, fund an address with 0.25 Goerli ETH, swap in the appropriate values for `<your_key>` and `<your_pk>` in this command, open a terminal window, and run the following:

TODO: update this to do the template thing.
TODO: update this to do the template thing. And add a bit about staying up to date with the latest.

```
git clone [email protected]:ProjectOpenSea/shipyard.git &&
cd shipyard &&
mkdir my-shipyard-based-project &&
cd my-shipyard-based-project &&
curl -L https://foundry.paradigm.xyz | bash &&
foundryup &&
forge init --template projectopensea/shipyard &&
forge build &&
export GOERLI_RPC='https://goerli.blockpi.network/v1/rpc/public &&
export ETHERSCAN_API_KEY='<your_key>' &&
export GOERLI_RPC_URL='https://goerli.blockpi.network/v1/rpc/public &&
export MY_ACTUAL_PK_BE_CAREFUL='<your_pk>' &&
forge create --rpc-url $GOERLI_RPC \
forge create --rpc-url $GOERLI_RPC_URL \
--private-key $MY_ACTUAL_PK_BE_CAREFUL \
--etherscan-api-key $ETHERSCAN_API_KEY \
--verify \
src/reference/ExampleNFT.sol:ExampleNFT
lib/shipyard-core/src/reference/ExampleNFT.sol:ExampleNFT \
--constructor-args "Tutorial Example NFT" "TENFT"
```

A quick breakdown of each step follows.

Clone the `shipyard` repository and change directories into it:
Create a directory, `cd` into it, :
```
git clone [email protected]:ProjectOpenSea/shipyard.git &&
cd shipyard
mkdir my-shipyard-based-project &&
cd my-shipyard-based-project &&
curl -L https://foundry.paradigm.xyz | bash
```

Install the `foundryup` up command and run it, which in turn installs forge, cast, anvil, and chisel:
Expand All @@ -77,25 +77,28 @@ curl -L https://foundry.paradigm.xyz | bash &&
foundryup
```

Create a new Foundry project based on Shipyard, which also initializes a new git repository.
```
forge init --template projectopensea/shipyard
```

Install dependencies and compile the contracts:
```
forge build
```

Set up your environment variables:
```
export GOERLI_RPC='https://goerli.blockpi.network/v1/rpc/public &&
export ETHERSCAN_API_KEY='<your_key>' &&
export GOERLI_RPC_URL='https://goerli.blockpi.network/v1/rpc/public &&
export MY_ACTUAL_PK_BE_CAREFUL='<your_pk>'
```

Run the `forge create` command, which deploys the contract:
```
forge create --rpc-url $GOERLI_RPC \
forge create --rpc-url $GOERLI_RPC_URL \
--private-key $MY_ACTUAL_PK_BE_CAREFUL \
--etherscan-api-key $ETHERSCAN_API_KEY \
--verify \
src/reference/ExampleNFT.sol:ExampleNFT
lib/shipyard-core/src/reference/ExampleNFT.sol:ExampleNFT \
--constructor-args "Tutorial Example NFT" "TENFT"
```

See https://book.getfoundry.sh/reference/forge/forge-create for more information on `forge create`.
Expand All @@ -104,7 +107,7 @@ Running this command deploys the example NFT contract, but it's a good way to ch

### Custom contract deployment tutorial

See [the full tutorial](docs/exampleNFTTutorial/Overview.md) for more detail on modifying the example contract, writing tests, deploying, and more.
See [the full tutorial](shipyardDocs/exampleNFTTutorial/Overview.md) for more detail on modifying the example contract, writing tests, deploying, and more.

### Reinitialize Submodules
When working across branches with different dependencies, submodules may need to be reinitialized. Run
Expand Down
20 changes: 20 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ fs_permissions = [{ access = "read", path = "./via_ir-out" }]
# bytecode is typically unaffected past ~1 million runs anyway
optimizer_runs = 99_999_999

[rpc_endpoints]
goerli = "${GOERLI_RPC_URL}"

# via_ir pipeline is very slow - use a separate profile to pre-compile and then use vm.getCode to deploy
[profile.via_ir]
via_ir = true
Expand Down Expand Up @@ -56,5 +59,22 @@ fs_permissions = [{ access = 'read-write', path = './test-ffi/' }]
[profile.ci-ffi.fuzz]
runs = 10

[profile.tutorial]
src = "lib/shipyard-core/src"
test = 'lib/shipyard-core/test'
out = "lib/shipyard-core/out"
libs = ["lib/shipyard-core/lib"]
remappings = [
'src/=lib/shipyard-core/src/',
'dynamic-traits/=lib/shipyard-core/lib/dynamic-traits/src/',
'forge-std/=lib/shipyard-core/lib/forge-std/src',
'openzeppelin-contracts/=lib/shipyard-core/lib/openzeppelin-contracts/contracts/',
'openzeppelin-contracts/contracts/=lib/shipyard-core/lib/openzeppelin-contracts/contracts/',
'seaport-types/=lib/shipyard-core/lib/seaport-types/src/',
'shipyard-core/=lib/shipyard-core/src/',
'solady-test/=lib/shipyard-core/lib/solady/test/',
'solady/=lib/shipyard-core/lib/solady/src/',
'solarray/=lib/shipyard-core/lib/solarray/src/',
]

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
1 change: 1 addition & 0 deletions lib/shipyard-core
Submodule shipyard-core added at 76fab0
13 changes: 8 additions & 5 deletions sample.env
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
## TESTING STUFF

# Use this profile to run the shipyard-core tests from the shipyard directory.
export FOUNDRY_PROFILE='tutorial'

# see https://book.getfoundry.sh/reference/config/testing#verbosity
FOUNDRY_VERBOSITY=3
export FOUNDRY_VERBOSITY=3

# see https://book.getfoundry.sh/reference/config/testing?highlight=FOUNDRY_FUZZ_RUNS#fuzz
FOUNDRY_FUZZ_RUNS=128
export FOUNDRY_FUZZ_RUNS=128


## COMPILER STUFF

# see https://book.getfoundry.sh/reference/config/solidity-compiler?highlight=via_ir#optimizer,
# https://book.getfoundry.sh/reference/config/overview?highlight=foundry_profile#profiles,
# and the foundry.toml file. You can uncomment this to enable compiling with the optimizer.
# FOUNDRY_PROFILE=via_ir
# export FOUNDRY_PROFILE=via_ir


## DEPLOYING STUFF
Expand All @@ -21,7 +24,7 @@ FOUNDRY_FUZZ_RUNS=128
# need for handling a private key directly. Stay tuned.
export MY_ACTUAL_PK_BE_CAREFUL=0

export GOERLI_RPC='https://goerli.infura.io/v3/<your_key>'
export ETH_RPC='https://mainnet.infura.io/v3/<your_key>'
export GOERLI_RPC_URL='https://goerli.infura.io/v3/<your_key>'
export ETH_RPC_URL='https://mainnet.infura.io/v3/<your_key>'

export ETHERSCAN_API_KEY='<your_key>'
17 changes: 17 additions & 0 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;

import "forge-std/Script.sol";

import { ExampleNFT } from "shipyard-core/reference/ExampleNFT.sol";

import "forge-std/console.sol";

contract Deploy is Script {
function run() public {
uint256 deployerPrivateKey = vm.envUint("MY_ACTUAL_PK_BE_CAREFUL");
vm.startBroadcast(deployerPrivateKey);
new ExampleNFT('Example', 'EXNFT');
vm.stopBroadcast();
}
}
155 changes: 155 additions & 0 deletions shipyardDocs/exampleNFTTutorial/CustomNFTFunctionality.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Changing the behavior of your NFT

Solidity supports inheritance, so instead of tampering with the NFT contracts that come with Shipyard directly, you'll create a new contract and have it inherit from one of the existing contracts. See [Solidity by Example](https://solidity-by-example.org/inheritance/) and [GeeksForGeeks](https://www.geeksforgeeks.org/solidity-inheritance/) for more info on inheritance. The practical takeway is:

- Make a new file in `src/` called `Dockmaster.sol`
- Import `shipyard-core/reference/AbstractNFT.sol:` and inherit from it (`contract Dockmaster is AbstractNFT { ...`)
- Override some functions (`name`, `symbol`, and `tokenURI` are good starting places, e.g. `function name() public pure override returns (string memory) { ...`)
- Add some other fun stuff if you want to

And you'll end up with something that might look like this:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import {json} from "shipyard-core/onchain/json.sol";
import {svg} from "shipyard-core/onchain/svg.sol";
import {LibString} from "solady/utils/LibString.sol";
import {Base64} from "solady/utils/Base64.sol";
import {Solarray} from "solarray/Solarray.sol";
import {Metadata, DisplayType} from "shipyard-core/onchain/Metadata.sol";
import {AbstractNFT} from "shipyard-core/reference/AbstractNFT.sol";
contract Dockmaster is AbstractNFT {
using LibString for string;
using LibString for uint256;
uint256 currentId;
event SayHiToMom(string message);
constructor() {
emit SayHiToMom("Hi Mom!");
}
function name() public pure override returns (string memory) {
return "Dockmaster";
}
function symbol() public pure override returns (string memory) {
return "DOCK";
}
function tokenURI(uint256 tokenId) public view override returns (string memory) {
return Metadata.base64JsonDataURI(stringURI(tokenId));
}
function stringURI(uint256 tokenId) internal view returns (string memory) {
return json.objectOf(
Solarray.strings(
json.property("name", string.concat("Dockmaster NFT #", tokenId.toString())),
json.property("description", string.concat("My dock has ", tokenId.toString(), " slips.")),
json.property("image", Metadata.svgDataURI(image(tokenId))),
_attribute(tokenId)
)
);
}
function _attribute(uint256 tokenId) internal view returns (string memory) {
string[] memory staticTraits = Solarray.strings(
Metadata.attribute({
traitType: "Slip Number",
value: tokenId.toString(),
displayType: DisplayType.Number
}),
Metadata.attribute({traitType: "Dock Side", value: tokenId % 2 == 0 ? "North" : "South"})
);
string[] memory dynamicTraits = _dynamicAttributes(tokenId);
string[] memory combined = new string[](staticTraits.length + dynamicTraits.length);
for (uint256 i = 0; i < staticTraits.length; i++) {
combined[i] = staticTraits[i];
}
for (uint256 i = 0; i < dynamicTraits.length; i++) {
combined[staticTraits.length + i] = dynamicTraits[i];
}
return json.rawProperty("attributes", json.arrayOf(combined));
}
function image(uint256 tokenId) internal pure returns (string memory) {
return svg.top({
props: string.concat(svg.prop("width", "500"), svg.prop("height", "500")),
children: string.concat(
svg.rect({
props: string.concat(svg.prop("width", "500"), svg.prop("height", "500"), svg.prop("fill", "lightgray"))
}),
svg.text({
props: string.concat(
svg.prop("x", "50%"),
svg.prop("y", "50%"),
svg.prop("dominant-baseline", "middle"),
svg.prop("text-anchor", "middle"),
svg.prop("font-size", "48"),
svg.prop("fill", "black")
),
children: string.concat("You're looking at slip #", tokenId.toString())
})
)
});
}
function mint(address to) public {
unchecked {
_mint(to, ++currentId);
}
}
function isOwnerOrApproved(uint256 tokenId, address addr) internal view virtual override returns (bool) {
return ownerOf(tokenId) == addr || getApproved(tokenId) == addr || isApprovedForAll(ownerOf(tokenId), addr);
}
function supportsInterface(bytes4 interfaceId) public view virtual override(DynamicTraits, ERC721) returns (bool) {
return DynamicTraits.supportsInterface(interfaceId) || ERC721.supportsInterface(interfaceId);
}
function myVeryOwnFunction() public {
emit SayHiToMom("Hey Mama!");
}
}
```

(You'll notice that this looks a lot like shipyard-core's ExampleNFT.sol, but it's special to me now!)

What's great about this is that the `ERC721ConduitPreapproved_Solady` I inheritied via `AbstractNFT` has already handled all the routine ERC721 functionality for me in an almost comically optimized way. So I can burn calories on making something cool, instead of reinventing the nonfungible token wheel. But when I call the `ownerOf` function on my contract, it's going to work as expected.

Now is the time to go wild. Make some wild generative art. Create a novel minting mechanic (only contract deployers are eligible?!). Build an AMM into it. Whatever idea brought you here in the first place, here's where you wire it up. Either bolt it on (like `SayHiToMom`) or override existing functionality (like `tokenURI`). Your NFT is as interesting as you make it.

And because I inherited `AbstractNFT`, which itself inherit `OnchainTraits`, I automatically get functionality that lets me manage on chain traits. So, now I want to add the following trait to token ID 1:

```
{
"trait_type": "Dock Material"
"value": "Aluminum"
}
```

To accomplish that, I'll need to first set a trait label by calling `setTraitLabel` with `bytes32(uint256(0x6d6174657269616c))` (the bytes encoded version of "material") as the `traitKey` and the following struct as the `TraitLabel` arg:

```
TraitLabel memory myFirstDynamicTrait {(
fullTraitKey: myFirstTraitKeyString,
traitLabel: myFirstLabelString,
acceptableValues: myFirstArrayOfAcceptableValuesStrings,
fullTraitValues: myFirstArrayOfFullTraitValues,
displayType: myFirstDisplayTypeEnumValue,
editors: myFirstEditorsValue;
)}
```

Look at `TraitLabelLib.sol` to get a better sense of how to set those values up.

Then I just call `setTrait` with `bytes32(uint256(0x6d6174657269616c))` as the `traitKey` argument, `1` as the `tokenID` argument, and `bytes32(uint256(<bytes32_value_of_my_trait>))` as the `trait` argument. The `_setTrait` function will store the value (`_traits[tokenId][traitKey] = value;`) and emit a `TraitUpdated` event, to put the world on notice that token ID 1 now has a new trait on it.

## Next up:

[Deploying for real](Deploying.md)
Loading

0 comments on commit cd79ff0

Please sign in to comment.