Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NO-JIRA] Add example for end to end crafting transaction #242

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions test/crafting/Crafting.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// SPDX-License-Identifier: Apache 2.0
pragma solidity 0.8.19;

import "forge-std/Test.sol";

import {DeployOperatorAllowlist} from "../utils/DeployAllowlistProxy.sol";
import {OperatorAllowlistUpgradeable} from "../../contracts/allowlist/OperatorAllowlistUpgradeable.sol";
import {ImmutableERC1155} from "../../contracts/token/erc1155/preset/ImmutableERC1155.sol";
import {ImmutableERC721} from "../../contracts/token/erc721/preset/ImmutableERC721.sol";
import {GuardedMulticaller} from "../../contracts/multicall/GuardedMulticaller.sol";

import {SigUtils} from "./SigUtils.sol";

contract CraftingTest is Test {
OperatorAllowlistUpgradeable public operatorAllowlist;
ImmutableERC1155 public game1155;
ImmutableERC721 public game721;
GuardedMulticaller public multicaller;
SigUtils public sigUtils;

uint256 public imtblPrivateKey = 1;
uint256 public gameStudioPrivateKey = 2;
uint256 public signingAuthorityPrivateKey = 3;
uint256 public playerPrivateKey = 4;

address public imtbl = vm.addr(imtblPrivateKey);
address public gameStudio = vm.addr(gameStudioPrivateKey);
address public signingAuthority = vm.addr(signingAuthorityPrivateKey);
address public player = vm.addr(playerPrivateKey);

address public proxyAddr;

string public multicallerName = "multicaller-name";
string public multicallerVersion = "multicaller-version";

function setUp() public {
DeployOperatorAllowlist deployScript = new DeployOperatorAllowlist();
proxyAddr = deployScript.run(imtbl, imtbl, imtbl);
operatorAllowlist = OperatorAllowlistUpgradeable(proxyAddr);

assertTrue(operatorAllowlist.hasRole(operatorAllowlist.REGISTRAR_ROLE(), imtbl));

game1155 = new ImmutableERC1155(
gameStudio, "test1155", "test-base-uri", "test-contract-uri", address(operatorAllowlist), gameStudio, 0
);

vm.prank(gameStudio);
game1155.grantMinterRole(gameStudio);
assertTrue(game1155.hasRole(game1155.MINTER_ROLE(), gameStudio));

game721 = new ImmutableERC721(
gameStudio, "test721", "TST", "test-base-uri", "test-contract-uri", address(operatorAllowlist), gameStudio, 0
);

// Deploy game studio's multicaller contract
multicaller = new GuardedMulticaller(gameStudio, multicallerName, multicallerVersion);
assertTrue(multicaller.hasRole(multicaller.DEFAULT_ADMIN_ROLE(), gameStudio));

// Add multicaller to operator allowlist
address[] memory allowlistTargets = new address[](1);
allowlistTargets[0] = address(multicaller);

vm.prank(imtbl);
operatorAllowlist.addAddressesToAllowlist(allowlistTargets);
assertTrue(operatorAllowlist.isAllowlisted(address(multicaller)));

// Grant minter role to the game studio
vm.startPrank(gameStudio);
game721.grantMinterRole(gameStudio);
assertTrue(game721.hasRole(game721.MINTER_ROLE(), gameStudio));

// Grant minter role to the multicaller contract
game721.grantMinterRole(address(multicaller));
assertTrue(game721.hasRole(game721.MINTER_ROLE(), address(multicaller)));
vm.stopPrank();

// Grant signer role to signing authority
vm.prank(gameStudio);
multicaller.grantMulticallSignerRole(signingAuthority);
assertTrue(multicaller.hasRole(multicaller.MULTICALL_SIGNER_ROLE(), signingAuthority));

// Permit required functions
GuardedMulticaller.FunctionPermit[] memory functionPermits = new GuardedMulticaller.FunctionPermit[](2);
GuardedMulticaller.FunctionPermit memory mintPermit = GuardedMulticaller.FunctionPermit(
address(game721),
game721.safeMint.selector,
true
);
GuardedMulticaller.FunctionPermit memory burnPermit = GuardedMulticaller.FunctionPermit(
address(game1155),
game1155.burnBatch.selector,
true
);
functionPermits[0] = mintPermit;
functionPermits[1] = burnPermit;
vm.prank(gameStudio);
multicaller.setFunctionPermits(functionPermits);
assertTrue(multicaller.isFunctionPermitted(address(game721), game721.safeMint.selector));
assertTrue(multicaller.isFunctionPermitted(address(game1155), game1155.burnBatch.selector));

sigUtils = new SigUtils(multicallerName, multicallerVersion, address(multicaller));
}

function testCraft() public {
// Game studio mints 10 of tokenID 1 on 1155 to player
vm.prank(gameStudio);
game1155.safeMint(player, 1, 10, "");
assertTrue(game1155.balanceOf(player, 1) == 10);

// Game studio mints 10 of tokenID 2 on 1155 to player
vm.prank(gameStudio);
game1155.safeMint(player, 2, 10, "");
assertTrue(game1155.balanceOf(player, 2) == 10);

// Perform a craft using the Multicaller
// - burn 1 of 1155 tokenID 1
// - burn 2 of 1155 tokenID 2
// - mint 1 721 to player

bytes32 referenceID = keccak256("testCraft:1");

address[] memory targets = new address[](2);
targets[0] = address(game1155);
targets[1] = address(game721);

bytes[] memory data = new bytes[](2);

uint256[] memory ids = new uint256[](2);
ids[0] = 1;
ids[1] = 2;

uint256[] memory values = new uint256[](2);
values[0] = 1;
values[1] = 2;

data[0] = abi.encodeWithSignature("burnBatch(address,uint256[],uint256[])", player, ids, values);
data[1] = abi.encodeWithSignature("safeMint(address,uint256)", player, 1);

uint256 deadline = block.timestamp + 10;

// Construct signature
bytes32 structHash = sigUtils.getTypedDataHash(referenceID, targets, data, deadline);

vm.startPrank(signingAuthority);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(signingAuthorityPrivateKey, structHash);
bytes memory signature = abi.encodePacked(r, s, v);
vm.stopPrank();

// Give multicaller approve to burn
vm.startPrank(player);
game1155.setApprovalForAll(address(multicaller), true);
assertTrue(game1155.isApprovedForAll(player, address(multicaller)));

multicaller.execute(signingAuthority, referenceID, targets, data, deadline, signature);
vm.stopPrank();

assertTrue(game1155.balanceOf(player, 1) == 9);
assertTrue(game1155.balanceOf(player, 2) == 8);
assertTrue(game721.balanceOf(player) == 1);
}
}
77 changes: 77 additions & 0 deletions test/crafting/SigUtils.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: Apache 2.0
pragma solidity 0.8.19;

contract SigUtils {
bytes32 internal _DOMAIN_SEPARATOR;

bytes32 internal constant MULTICALL_TYPEHASH =
keccak256("Multicall(bytes32 ref,address[] targets,bytes[] data,uint256 deadline)");

constructor(string memory _name, string memory _version, address _verifyingContract) {
_DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(_name)),
keccak256(bytes(_version)),
block.chainid,
_verifyingContract
)
);
}

/**
*
* @dev Returns hash of array of bytes
*
* @param _data Array of bytes
*/
function hashBytesArray(bytes[] memory _data) public pure returns (bytes32) {
bytes32[] memory hashedBytesArr = new bytes32[](_data.length);
for (uint256 i = 0; i < _data.length; i++) {
hashedBytesArr[i] = keccak256(_data[i]);
}
return keccak256(abi.encodePacked(hashedBytesArr));
}

function getStructHash(
bytes32 _reference,
address[] calldata _targets,
bytes[] calldata _data,
uint256 _deadline
) internal pure returns (bytes32)
{
return keccak256(
abi.encode(
MULTICALL_TYPEHASH,
_reference,
keccak256(abi.encodePacked(_targets)),
hashBytesArray(_data),
_deadline
)
);
}

/**
*
* @dev Returns EIP712 message hash for given parameters
*
* @param _reference Reference
* @param _targets List of addresses to call
* @param _data List of call data
* @param _deadline Expiration timestamp
*/
function getTypedDataHash(
bytes32 _reference,
address[] calldata _targets,
bytes[] calldata _data,
uint256 _deadline
) public view returns (bytes32) {
return keccak256(
abi.encodePacked(
"\x19\x01",
_DOMAIN_SEPARATOR,
getStructHash(_reference, _targets, _data, _deadline)
)
);
}
}
Loading
Loading