Skip to content

Commit

Permalink
Added documentation for Base64 and references from ERC1155 and ERC721
Browse files Browse the repository at this point in the history
  • Loading branch information
ernestognw committed Oct 8, 2021
1 parent df3d838 commit 7c187e0
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 29 deletions.
46 changes: 20 additions & 26 deletions contracts/utils/Base64.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@ pragma solidity ^0.8.0;
* @dev Provides a set of functions to operate with Base64 strings.
*/
library Base64 {
/**
* @dev Base64 Encoding/Decoding Table
*/
/**
* @dev Base64 Encoding/Decoding Table
*/
string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

/**
* @dev Converts a `bytes` to its Bytes64 `string` representation.
* Inspired by Brecht Devos (Brechtpd) implementation - MIT licence
* https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol
*/
function encode(bytes memory data) internal pure returns (string memory) {
/**
* Inspired by Brecht Devos (Brechtpd) implementation - MIT licence
* https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol
*/
if (data.length == 0) return "";

// Loads the table into memory
Expand All @@ -25,7 +27,7 @@ library Base64 {
// Encoding takes 3 bytes chunks of binary data from `bytes` data parameter
// and split into 4 numbers of 6 bits.
// The final Base64 length should be `bytes` data length multiplied by 4/3 rounded up
// - `data.length + 2` -> Round up
// - `data.length + 2` -> Round up
// - `/ 3` -> Number of 3-bytes chunks
// - `4 *` -> 4 characters for each chunk
uint256 encodedLen = 4 * ((data.length + 2) / 3);
Expand All @@ -48,7 +50,11 @@ library Base64 {
let resultPtr := add(result, 32)

// Run over the input, 3 bytes at a time
for { } lt(dataPtr, endPtr) { } {
for {

} lt(dataPtr, endPtr) {

} {
// Advance 3 bytes
dataPtr := add(dataPtr, 3)
let input := mload(dataPtr)
Expand All @@ -61,28 +67,16 @@ library Base64 {
// and finally write it in the result pointer but with a left shift
// of 256 (1 byte) - 8 (1 ASCII char) = 248 bits

mstore(
resultPtr,
shl(248, mload(add(tablePtr, and(shr(18, input), 0x3F))))
)
mstore(resultPtr, shl(248, mload(add(tablePtr, and(shr(18, input), 0x3F)))))
resultPtr := add(resultPtr, 1) // Advance

mstore(
resultPtr,
shl(248, mload(add(tablePtr, and(shr(12, input), 0x3F))))
)

mstore(resultPtr, shl(248, mload(add(tablePtr, and(shr(12, input), 0x3F)))))
resultPtr := add(resultPtr, 1) // Advance

mstore(
resultPtr,
shl(248, mload(add(tablePtr, and(shr(6, input), 0x3F))))
)

mstore(resultPtr, shl(248, mload(add(tablePtr, and(shr(6, input), 0x3F)))))
resultPtr := add(resultPtr, 1) // Advance

mstore(
resultPtr,
shl(248, mload(add(tablePtr, and(input, 0x3F))))
)

mstore(resultPtr, shl(248, mload(add(tablePtr, and(input, 0x3F)))))
resultPtr := add(resultPtr, 1) // Advance
}

Expand Down
4 changes: 3 additions & 1 deletion contracts/utils/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/

Miscellaneous contracts and libraries containing utility functions you can use to improve security, work with new data types, or safely use low-level primitives.

The {Address}, {Arrays} and {Strings} libraries provide more operations related to these native data types, while {SafeCast} adds ways to safely convert between the different signed and unsigned numeric types.
The {Address}, {Arrays}, {Base64} and {Strings} libraries provide more operations related to these native data types, while {SafeCast} adds ways to safely convert between the different signed and unsigned numeric types.
{Multicall} provides a function to batch together multiple calls in a single external call.

For new data types:
Expand Down Expand Up @@ -94,6 +94,8 @@ Note that, in all cases, accounts simply _declare_ their interfaces, but they ar

{{Arrays}}

{{Base64}}

{{Counters}}

{{Strings}}
Expand Down
4 changes: 3 additions & 1 deletion docs/modules/ROOT/pages/erc1155.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ The JSON document for token ID 2 might look something like:

For more information about the metadata JSON Schema, check out the https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1155.md#erc-1155-metadata-uri-json-schema[ERC-1155 Metadata URI JSON Schema].

NOTE: you'll notice that the item's information is included in the metadata, but that information isn't on-chain! So a game developer could change the underlying metadata, changing the rules of the game!
NOTE: You'll notice that the item's information is included in the metadata, but that information isn't on-chain! So a game developer could change the underlying metadata, changing the rules of the game!

TIP: If you'd like to put all item information on-chain, you can extend ERC721 to do so (though it will be rather costly) by providing a xref:utilities.adoc#base64[`Base64`] Data URI with the JSON schema encoded. You could also leverage IPFS to store the URI information, but these techniques are out of the scope of this overview guide

[[sending-to-contracts]]
== Sending Tokens to Contracts
Expand Down
4 changes: 3 additions & 1 deletion docs/modules/ROOT/pages/erc721.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ This `tokenURI` should resolve to a JSON document that might look something like

For more information about the `tokenURI` metadata JSON Schema, check out the https://eips.ethereum.org/EIPS/eip-721[ERC721 specification].

NOTE: you'll notice that the item's information is included in the metadata, but that information isn't on-chain! So a game developer could change the underlying metadata, changing the rules of the game! If you'd like to put all item information on-chain, you can extend ERC721 to do so (though it will be rather costly). You could also leverage IPFS to store the tokenURI information, but these techniques are out of the scope of this overview guide.
NOTE: You'll notice that the item's information is included in the metadata, but that information isn't on-chain! So a game developer could change the underlying metadata, changing the rules of the game!

TIP: If you'd like to put all item information on-chain, you can extend ERC721 to do so (though it will be rather costly) by providing a xref:utilities.adoc#base64[`Base64`] Data URI with the JSON schema encoded. You could also leverage IPFS to store the tokenURI information, but these techniques are out of the scope of this overview guide.

[[Presets]]
== Preset ERC721 contract
Expand Down
48 changes: 48 additions & 0 deletions docs/modules/ROOT/pages/utilities.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,54 @@ Want to check if an address is a contract? Use xref:api:utils.adoc#Address[`Addr

Want to keep track of some numbers that increment by 1 every time you want another one? Check out xref:api:utils.adoc#Counters[`Counters`]. This is useful for lots of things, like creating incremental identifiers, as shown on the xref:erc721.adoc[ERC721 guide].

=== Base64

xref:api:utils.adoc#Base64[`Base64`] util allows you to transform `bytes32` data into its Base64 `string` representation.

This is specially useful to build URL-safe tokenURIs for both xref:api:token/ERC721.adoc#IERC721Metadata-tokenURI-uint256-[`ERC721`] or xref:api:token/ERC1155.adoc#IERC1155MetadataURI-uri-uint256-[`ERC1155`]. This library provides a clever way to serve URL-safe https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URIs/[Data URI] compliant strings to serve on-chain data structures.

Consider this is an example to send JSON Metadata through in a Base64 Data URI using an ERC721:

[source, solidity]
----
// contracts/My721Token.sol
// SPDX-License-Identifier: MIT
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/Base64.sol";
contract My721Token is ERC721 {
using Strings for uint256;
using Base64 for bytes;
constructor() ERC721("My721Token", "MTK") {}
...
function tokenURI(uint256 tokenId)
public
pure
override
returns (string memory)
{
bytes memory dataURI = abi.encodePacked(
'{
"name": "My721Token #', tokenId.toString(), '"',
// Replace with extra ERC721 Metadata properties
'}'
);
return string(
abi.encodePacked(
"data:application/json;base64,",
dataURI.encode()
)
);
}
}
----

=== Multicall

The `Multicall` abstract contract comes with a `multicall` function that bundles together multiple calls in a single external call. With it, external accounts may perform atomic operations comprising several function calls. This is not only useful for EOAs to make multiple calls in a single transaction, it's also a way to revert a previous call if a later one fails.
Expand Down

0 comments on commit 7c187e0

Please sign in to comment.