Skip to content

Commit

Permalink
Merge pull request #125 from Hats-Protocol/multicallable
Browse files Browse the repository at this point in the history
Multicallable
  • Loading branch information
spengrah committed Jul 10, 2023
2 parents c6d0da7 + b43ad0d commit b4cdfbd
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 12 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "lib/solbase"]
path = lib/solbase
url = https://github.com/sol-dao/solbase
[submodule "lib/solady"]
path = lib/solady
url = https://github.com/vectorized/solady
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,10 @@ The wearer of a Hat can "take off" their Hat via `Hats.renounceHat`. This burns

<p align="right">(<a href="#table-of-contents">back to contents</a>)</p>

### Multicall

This contract inherits from [Multicallable](https://github.com/Vectorized/solady/blob/main/src/utils/Multicallable.sol), which adds a non-payable `multicall` function to the contract. This enables EOAs to make multiple calls to the contract atomically, unlocking a number of useful batch operations that were previously only available to smart contracts.

## License

Distributed under the AGPLv3 License. See `LICENSE.txt` for more information.
Expand Down
3 changes: 2 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ solc = "0.8.17"
remappings = [
"ds-test/=lib/forge-std/lib/ds-test/src/",
"forge-std/=lib/forge-std/src/",
"solbase/=lib/solbase/src/"
"solbase/=lib/solbase/src/",
"solady/=lib/solady/src/"
]

[fmt]
Expand Down
2 changes: 1 addition & 1 deletion lib/forge-std
Submodule forge-std updated 48 files
+138 −0 .github/workflows/ci.yml
+29 −0 .github/workflows/sync.yml
+0 −27 .github/workflows/tests.yml
+1 −1 .gitignore
+1 −1 LICENSE-APACHE
+1 −1 LICENSE-MIT
+8 −4 README.md
+19 −0 foundry.toml
+1 −1 lib/ds-test
+4 −4 package.json
+35 −0 src/Base.sol
+24 −41 src/Script.sol
+376 −0 src/StdAssertions.sol
+229 −0 src/StdChains.sol
+716 −0 src/StdCheats.sol
+15 −0 src/StdError.sol
+92 −0 src/StdInvariant.sol
+122 −61 src/StdJson.sol
+43 −0 src/StdMath.sol
+327 −0 src/StdStorage.sol
+333 −0 src/StdStyle.sol
+198 −0 src/StdUtils.sol
+30 −1,134 src/Test.sol
+451 −199 src/Vm.sol
+406 −386 src/console2.sol
+105 −0 src/interfaces/IERC1155.sol
+12 −0 src/interfaces/IERC165.sol
+43 −0 src/interfaces/IERC20.sol
+190 −0 src/interfaces/IERC4626.sol
+164 −0 src/interfaces/IERC721.sol
+73 −0 src/interfaces/IMulticall3.sol
+13,248 −0 src/safeconsole.sol
+0 −20 src/test/Script.t.sol
+0 −282 src/test/StdCheats.t.sol
+0 −200 src/test/StdMath.t.sol
+454 −57 test/StdAssertions.t.sol
+215 −0 test/StdChains.t.sol
+559 −0 test/StdCheats.t.sol
+5 −11 test/StdError.t.sol
+212 −0 test/StdMath.t.sol
+46 −84 test/StdStorage.t.sol
+110 −0 test/StdStyle.t.sol
+342 −0 test/StdUtils.t.sol
+10 −0 test/compilation/CompilationScript.sol
+10 −0 test/compilation/CompilationScriptBase.sol
+10 −0 test/compilation/CompilationTest.sol
+10 −0 test/compilation/CompilationTestBase.sol
+0 −0 test/fixtures/broadcast.log.json
1 change: 1 addition & 0 deletions lib/solady
Submodule solady added at 7175c2
4 changes: 4 additions & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
solady/=lib/solady/src/
solbase/=lib/solbase/src/
12 changes: 5 additions & 7 deletions script/Hats.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { Script, console2 } from "forge-std/Script.sol";
import { Hats } from "../src/Hats.sol";

contract DeployHats is Script {
string public constant baseImageURI = "ipfs://bafybeigcimbqwfajsnhoq7fqnbdllz7kye7cpdy3adj2sob3wku2llu5bi";
string public constant baseImageURI = "ipfs://bafkreiflezpk3kjz6zsv23pbvowtatnd5hmqfkdro33x5mh2azlhne3ah4";

string public constant name = "Hats Protocol v1.0"; // increment this each deployment
string public constant name = "Hats Protocol v1"; // increment this each deployment

bytes32 internal constant SALT = bytes32(abi.encode(0x4a75)); // ~ H(4) A(a) T(7) S(5)

Expand All @@ -30,12 +30,10 @@ contract DeployHats is Script {
console2.log("Hats contract: ", address(hats));
}

// forge script script/Hats.s.sol:DeployHats -f mainnet
// forge script script/Hats.s.sol:DeployHats -f mainnet --broadcast --verify
// forge script script/Hats.s.sol:DeployHats -f goerli
// forge script script/Hats.s.sol:DeployHats -f goerli --broadcast --verify

// forge script script/Hats.s.sol:DeployHats --rpc-url http://localhost:8545 --broadcast

// forge verify-contract --chain-id 1 --num-of-optimizations 10000 --watch --constructor-args $(cast abi-encode "constructor(string,string)" "Hats Protocol v1.0" "ipfs://bafybeigcimbqwfajsnhoq7fqnbdllz7kye7cpdy3adj2sob3wku2llu5bi") --compiler-version v0.8.17 0x9D2dfd6066d5935267291718E8AA16C8Ab729E9d src/Hats.sol:Hats --etherscan-api-key $ETHERSCAN_KEY
// forge verify-contract --chain-id 5 --num-of-optimizations 10000 --watch --constructor-args $(cast abi-encode "constructor(string,string)" "Hats Protocol v1" "ipfs://bafkreiflezpk3kjz6zsv23pbvowtatnd5hmqfkdro33x5mh2azlhne3ah4") --compiler-version v0.8.17 0x3bc1A0Ad72417f2d411118085256fC53CBdDd137 src/Hats.sol:Hats --etherscan-api-key $ETHERSCAN_KEY
}

contract DeployHatsAndMintTopHat is Script {
Expand Down
10 changes: 7 additions & 3 deletions src/Hats.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,16 @@ import "./Interfaces/IHatsToggle.sol";
import "./Interfaces/IHatsEligibility.sol";
import "solbase/utils/Base64.sol";
import "solbase/utils/LibString.sol";
import "solady/utils/Multicallable.sol";

/// @title Hats Protocol
/// @title Hats Protocol v1
/// @notice Hats are DAO-native, revocable, and programmable roles that are represented as non-transferable ERC-1155-similar tokens for composability
/// @dev This is a multitenant contract that can manage all hats for a given chain. While it fully implements the ERC1155 interface, it does not fully comply with the ERC1155 standard.
/// @dev This is a multi-tenant contract that can manage all hats for a given chain. While it fully implements the ERC1155 interface, it does not fully comply with the ERC1155 standard.
/// @author Haberdasher Labs
contract Hats is IHats, ERC1155, HatsIdUtilities {
contract Hats is IHats, ERC1155, Multicallable, HatsIdUtilities {
/// @notice This contract's version is labeled v1. Previous versions labeled similarly as v1 and v1.0 are deprecated,
/// and should be treated as beta deployments.

/*//////////////////////////////////////////////////////////////
HATS DATA MODELS
//////////////////////////////////////////////////////////////*/
Expand Down
109 changes: 109 additions & 0 deletions test/Hats.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2395,3 +2395,112 @@ contract MalformedInputsTests is TestSetup2 {
hats.checkHatStatus(secondHatId);
}
}

contract MulticallTests is TestSetup {
bytes[] data;
string topDeets;
string secondDeets;
string topImage;
string secondImage;

function test_mintNewTopHat_andCreateHat() public {
topHatId = 0x00000002_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000;
secondHatId = 0x00000002_0001_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000;
topDeets = "multicall tophat";
secondDeets = "multicall second hat";
topImage = "hatsprotocol.eth/tophat.png";
secondImage = "hatsprotocol.eth/secondhat.png";

// craft the data for the mintTopHat call
bytes memory mintTopHatData =
abi.encodeWithSignature("mintTopHat(address,string,string)", topHatWearer, topDeets, topImage);

// craft the data for the createHat call
bytes memory createHatData = abi.encodeWithSignature(
"createHat(uint256,string,uint32,address,address,bool,string)",
topHatId,
secondDeets,
500,
_eligibility,
_toggle,
true,
secondImage
);

// craft the data for the multicall
data = new bytes[](2);
data[0] = mintTopHatData;
data[1] = createHatData;

// expect hat created, hat minted, and hat created events
vm.expectEmit();
emit HatCreated(topHatId, topDeets, 1, address(0), address(0), false, topImage);
vm.expectEmit();
emit TransferSingle(topHatWearer, address(0), topHatWearer, topHatId, 1);
vm.expectEmit();
emit HatCreated(secondHatId, secondDeets, 500, _eligibility, _toggle, true, secondImage);

// call the multicall from topHatWearer
vm.prank(topHatWearer);
hats.multicall(data);

// check that the hats were created
assertTrue(hats.isWearerOfHat(topHatWearer, topHatId));
assertEq(hats.getHatMaxSupply(secondHatId), 500);
}

function test_editHat() public {
// create a mutable hat
test_mintNewTopHat_andCreateHat();

// craft the data for the details change
bytes memory changeDetailsData =
abi.encodeWithSignature("changeHatDetails(uint256,string)", secondHatId, "new details");

// craft the data for the image change
bytes memory changeImageData =
abi.encodeWithSignature("changeHatImageURI(uint256,string)", secondHatId, "hatsprotocol.eth/newimage.png");

// craft the data for the eligibility change
bytes memory changeEligibilityData =
abi.encodeWithSignature("changeHatEligibility(uint256,address)", secondHatId, address(1234));

// craft the data for the toggle change
bytes memory changeToggleData =
abi.encodeWithSignature("changeHatToggle(uint256,address)", secondHatId, address(5678));

// craft the data for the max supply change
bytes memory changeMaxSupplyData =
abi.encodeWithSignature("changeHatMaxSupply(uint256,uint32)", secondHatId, 1000);

// craft the data to make the hat immutable
bytes memory makeHatImmutableData = abi.encodeWithSignature("makeHatImmutable(uint256)", secondHatId);

// craft the data for the multicall
data = new bytes[](6);
data[0] = changeDetailsData;
data[1] = changeImageData;
data[2] = changeEligibilityData;
data[3] = changeToggleData;
data[4] = changeMaxSupplyData;
data[5] = makeHatImmutableData;

// expect the details, image, eligibility, toggle, max supply, and immutable events
vm.expectEmit();
emit HatDetailsChanged(secondHatId, "new details");
vm.expectEmit();
emit HatImageURIChanged(secondHatId, "hatsprotocol.eth/newimage.png");
vm.expectEmit();
emit HatEligibilityChanged(secondHatId, address(1234));
vm.expectEmit();
emit HatToggleChanged(secondHatId, address(5678));
vm.expectEmit();
emit HatMaxSupplyChanged(secondHatId, 1000);
vm.expectEmit();
emit HatMutabilityChanged(secondHatId);

// call the multicall from topHatWearer
vm.prank(topHatWearer);
hats.multicall(data);
}
}

0 comments on commit b4cdfbd

Please sign in to comment.