How does EthBuddies work?
The Token Contract
Let's start with the Token contract, it is an Erc721Royalty and Ownable.
We notice that the constructor takes four arguments name, symbol, metadataGenerator, and contractMetadataGenerator.
-
nameis the contract name, set to "Eth Buddies" at deployment. -
symbolis the contract symbol, set to "ETHBUDDIES" at deployment. -
metadataGeneratoris address of the contract that generates the metadata and image for each eth buddy. -
contractMetadataGeneratoris address of the contract that generates thecontractURI.
The next notable part of Token is the safeMint method that requires 1 ETH to run and mints a new buddy as long as it hasn't been minted before.
The proceeds from minting are withdrawable by the owner using the withdraw method.
Metadata Generators
We can see that there are two interfaces IMetadataGenerator and IContractMetadataGenerator.
We created the two interfaces to separate token ownership state from the metadata generation in case we want to improve or discover a bug in either of the generators.
interface IContractMetadataGenerator {
function generateContractMetadata() external view returns (string memory);
}
IContractMetadataGenerator generates the contract metadata and is less interesting as current contract that implements the interface simply returns a string from storage.
interface IMetadataGenerator {
function generateMetadata(uint256 tokenId) external view returns (string memory);
}
IMetadataGenerator on the otherhand is much more interesting, it accepts a tokenId and returns the corresponding tokenURI string.
In our case this string is a base64 encoded json object with the svg of the buddy included in it.
This means that the buddies don't require a running server.
Generating the svgs
The magic of eth buddies starts in the method generateSvg. This method creates an svg by decoding our space optimized svg paths that are stored on chain. There are a few tricks that we had to use to get this to work:
-
Convert the
tokenIdto the attributes that make up each buddy -
Create a
StringBuilderto avoid the gas issues caused by naive string concatenation - Efficiently encode svg path commands by reducing the possible values of the path points
- Use a fixed point representation for the points on the curve to preserve the fidelity of buddies
Splitting the token id
We split the token id into a three digit representation and use each digit to determine what attributes the buddy has. Then we can look up and load the attributes from storage.
The StringBuilder
The string builder uses a bytes buffer and an offset counter to efficiently build a string without incurring the gas costs of repeated calls to ethabi.encodePacked.
We essentially avoid the O(n^2) complexity of naively building a string in solidity.
ethabi.encodePacked creates a string with the length equal to the sum of the length of the inputs, this means that we need to reallocate and memcpy every call, rather than amortizing over calls as string implementations commonly do.
Path decoding
We will now look at PathLib.
XML paths are encoded as a sequence of u8 or (u16, u16).
We further require that the most significant bit of each component of each point is zero.
We can thus use the msb to determine if we are reading a command msb=1 or a point msb=0.
So if we see {128, 129, 130} we know that we have a command.
We simply iterate over our encoded path and build a string with proper white space and commas.
Fixed point scheme
We can use a fixed point scheme to allow us to keep the image size relatively normal (1000x1000).
So we remap [0, u15::max] to [0.1, u15::max / 10].
Generating the json
All we have left to do is construct the metadata object and base64 encode.