Skip to content
Merged
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
65 changes: 31 additions & 34 deletions the-guild-smart-contracts/script/FullDeploymentScript.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,72 +7,69 @@ import {AttestationRequestData, AttestationRequest} from "eas-contracts/IEAS.sol
import {SchemaRegistry} from "eas-contracts/SchemaRegistry.sol";
import {TheGuildActivityToken} from "../src/TheGuildActivityToken.sol";
import {TheGuildAttestationResolver} from "../src/TheGuildAttestationResolver.sol";
import {TheGuildInternalResolver} from "../src/TheGuildInternalResolver.sol";
import {TheGuildBadgeRegistry} from "../src/TheGuildBadgeRegistry.sol";
import {TheGuildBadgeRanking} from "../src/TheGuildBadgeRanking.sol";
import {EASUtils} from "./utils/EASUtils.s.sol";
import {console} from "forge-std/console.sol";

contract FullDeploymentScript is Script {
function run() public {
EAS eas;
bytes32 salt = bytes32("theguild_v_0.1.3");
// EAS addresses per https://github.com/ethereum-attestation-service/eas-contracts deployments
// Base mainnet (8453) and Base Goerli/Sepolia (84531/84532) use the canonical predeploy 0x...21
// Optimism mainnet (10) and OP Sepolia (11155420) also use canonical 0x...21
EAS eas = EAS(EASUtils.getEASAddress(vm));

eas = EAS(EASUtils.getEASAddress(vm));

// Derive deployer EOA from PRIVATE_KEY to keep ownership consistent under CREATE2 factory

uint256 pk = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(pk);

vm.startBroadcast(pk);

// Deploy activity token via CREATE2 with initial owner set to EOA (tx.origin)

TheGuildActivityToken activityToken = new TheGuildActivityToken{
salt: salt
}(deployer);

// Deploy or attach to existing badge registry via CREATE2 (needs to exist before resolver)
TheGuildBadgeRegistry badgeRegistry = new TheGuildBadgeRegistry{
salt: salt
}();

// Deploy resolver via CREATE2

TheGuildAttestationResolver resolver = new TheGuildAttestationResolver{
salt: salt
}(eas, activityToken, badgeRegistry);
// Transfer ownership from EOA to resolver so it can mint on attest


activityToken.transferOwnership(address(resolver));

// Register TheGuild Schema
string memory schema = "bytes32 badgeName, bytes justification";

SchemaRegistry schemaRegistry = SchemaRegistry(
EASUtils.getSchemaRegistryAddress(vm)
);
string memory schema = "bytes32 badgeName, bytes justification";
bytes32 schemaId = schemaRegistry.register(schema, resolver, true);
console.logString("Schema ID:");
console.logBytes32(schemaId);

console.log("Badge Attestation Schema ID:", vm.toString(schemaId));

// Create some badges
badgeRegistry.createBadge(
bytes32("Rust"),
bytes("Know how to code in Rust")
);
badgeRegistry.createBadge(
bytes32("Solidity"),
bytes("Know how to code in Solidity")
);
badgeRegistry.createBadge(
bytes32("TypeScript"),
bytes("Know how to code in TypeScript")
// This deployment sets 'deployer' as the initial owner and authorized attester
TheGuildInternalResolver internalResolver = new TheGuildInternalResolver{
salt: salt
}(eas, deployer);
Comment thread
joelamouche marked this conversation as resolved.


string memory skillSchema = "string skillDescription, bytes32[] linkedBadges";
bytes32 skillSchemaId = schemaRegistry.register(
skillSchema,
internalResolver,
true
);

console.log("Skill Badge Schema ID:", vm.toString(skillSchemaId));

// Deploy or attach to existing badge ranking via CREATE2

new TheGuildBadgeRanking{salt: salt}(badgeRegistry);

// Create some attestations

AttestationRequestData memory data = AttestationRequestData({
// TheGuild test account
recipient: address(0x6cfD0753EC4da15Dcb418E11e921C0665c1d1cBf),
expirationTime: 0,
revocable: true,
Expand All @@ -81,11 +78,11 @@ contract FullDeploymentScript is Script {
value: 0
});

AttestationRequest memory request = AttestationRequest({
eas.attest(AttestationRequest({
schema: schemaId,
data: data
});
eas.attest(request);
}));

vm.stopBroadcast();
}
}
}
46 changes: 46 additions & 0 deletions the-guild-smart-contracts/src/TheGuildInternalResolver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {SchemaResolver} from "eas-contracts/resolver/SchemaResolver.sol";
import {IEAS, Attestation} from "eas-contracts/IEAS.sol";
import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol";

/// @title TheGuildInternalResolver
/// @notice EAS schema resolver that restricts attestations to authorized Guild accounts only.
contract TheGuildInternalResolver is SchemaResolver, Ownable {
mapping(address => bool) private _authorizedAttesters;

event AttesterAuthorized(address indexed attester, bool authorized);

constructor(IEAS eas, address initialOwner) SchemaResolver(eas) Ownable(initialOwner) {
_authorizedAttesters[initialOwner] = true;
emit AttesterAuthorized(initialOwner, true);
}

/// @notice Authorize or deauthorize an account to create attestations.
function setAuthorizedAttester(address attester, bool authorized) external onlyOwner {
_authorizedAttesters[attester] = authorized;
emit AttesterAuthorized(attester, authorized);
}

/// @notice Check if an account is an authorized attester.
function isAuthorizedAttester(address attester) public view returns (bool) {
return _authorizedAttesters[attester];
}

/// @inheritdoc SchemaResolver
function onAttest(
Attestation calldata attestation,
uint256
) internal view override returns (bool) {
return _authorizedAttesters[attestation.attester];
}

/// @inheritdoc SchemaResolver
function onRevoke(
Attestation calldata attestation,
uint256
) internal view override returns (bool) {
return _authorizedAttesters[attestation.attester];
}
}
214 changes: 214 additions & 0 deletions the-guild-smart-contracts/test/TheGuildInternalResolver.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {Test} from "forge-std/Test.sol";
import {TheGuildInternalResolver} from "../src/TheGuildInternalResolver.sol";


import {EAS} from "eas-contracts/EAS.sol";
import {SchemaRegistry} from "eas-contracts/SchemaRegistry.sol";
import {IEAS, AttestationRequest, AttestationRequestData, Attestation, RevocationRequestData, RevocationRequest} from "eas-contracts/IEAS.sol";

contract TheGuildInternalResolverTest is Test {
TheGuildInternalResolver private resolver;
SchemaRegistry private schemaRegistry;
EAS private eas;

address private owner = address(this);
address private authorizedAttester = address(0xA11CE);
address private unauthorizedAttester = address(0xBEEF);
address private recipient = address(0xCAFE);

function setUp() public {

schemaRegistry = new SchemaRegistry();
eas = new EAS(schemaRegistry);


resolver = new TheGuildInternalResolver(
IEAS(address(eas)),
owner
);
}

function _registerSchema() internal returns (bytes32) {

string memory schema = "string skillDescription, bytes32[] linkedBadges";

bytes32 schemaId = schemaRegistry.register(schema, resolver, true);
return schemaId;
}

function test_AttestationByOwnerSucceeds() public {
bytes32 schemaId = _registerSchema();

bytes32[] memory linkedBadges = new bytes32[](2);
linkedBadges[0] = bytes32("Solidity");
linkedBadges[1] = bytes32("Rust");

AttestationRequestData memory data = AttestationRequestData({
recipient: recipient,
expirationTime: 0,
revocable: true,
refUID: bytes32(0),
data: abi.encode("Master of Smart Contracts", linkedBadges),
value: 0
});

AttestationRequest memory request = AttestationRequest({
schema: schemaId,
data: data
});


eas.attest(request);

}

function test_AttestationByAuthorizedAttesterSucceeds() public {
bytes32 schemaId = _registerSchema();
resolver.setAuthorizedAttester(authorizedAttester, true);

bytes32[] memory linkedBadges = new bytes32[](1);
linkedBadges[0] = bytes32("Solidity");

AttestationRequestData memory data = AttestationRequestData({
recipient: recipient,
expirationTime: 0,
revocable: true,
refUID: bytes32(0),
data: abi.encode("Solidity Dev", linkedBadges),
value: 0
});

AttestationRequest memory request = AttestationRequest({
schema: schemaId,
data: data
});

vm.prank(authorizedAttester);
eas.attest(request);

}

function test_AttestationByUnauthorizedAttesterFails() public {
bytes32 schemaId = _registerSchema();

bytes32[] memory linkedBadges = new bytes32[](1);
linkedBadges[0] = bytes32("Solidity");

AttestationRequestData memory data = AttestationRequestData({
recipient: recipient,
expirationTime: 0,
revocable: true,
refUID: bytes32(0),
data: abi.encode("Solidity Dev", linkedBadges),
value: 0
});

AttestationRequest memory request = AttestationRequest({
schema: schemaId,
data: data
});

vm.prank(unauthorizedAttester);
vm.expectRevert();
eas.attest(request);
}

function test_RevocationByAuthorizedAttesterSucceeds() public {
bytes32 schemaId = _registerSchema();
resolver.setAuthorizedAttester(authorizedAttester, true);

bytes32[] memory linkedBadges = new bytes32[](1);
linkedBadges[0] = bytes32("Solidity");

AttestationRequestData memory data = AttestationRequestData({
recipient: recipient,
expirationTime: 0,
revocable: true,
refUID: bytes32(0),
data: abi.encode("Solidity Dev", linkedBadges),
value: 0
});

AttestationRequest memory request = AttestationRequest({
schema: schemaId,
data: data
});

vm.prank(authorizedAttester);
bytes32 uid = eas.attest(request);

vm.prank(authorizedAttester);
eas.revoke(
RevocationRequest({
schema: schemaId,
data: RevocationRequestData({uid: uid, value: 0})
})
);
// success == no revert
}

function test_RevocationByUnauthorizedAttesterFails() public {
bytes32 schemaId = _registerSchema();
resolver.setAuthorizedAttester(authorizedAttester, true);

bytes32[] memory linkedBadges = new bytes32[](1);
linkedBadges[0] = bytes32("Solidity");

AttestationRequestData memory data = AttestationRequestData({
recipient: recipient,
expirationTime: 0,
revocable: true,
refUID: bytes32(0),
data: abi.encode("Solidity Dev", linkedBadges),
value: 0
});

AttestationRequest memory request = AttestationRequest({
schema: schemaId,
data: data
});

vm.prank(authorizedAttester);
bytes32 uid = eas.attest(request);

vm.prank(unauthorizedAttester);
vm.expectRevert();
eas.revoke(
RevocationRequest({
schema: schemaId,
data: RevocationRequestData({uid: uid, value: 0})
})
);
}

function test_DeauthorizeAttester() public {
bytes32 schemaId = _registerSchema();
resolver.setAuthorizedAttester(authorizedAttester, true);
assertTrue(resolver.isAuthorizedAttester(authorizedAttester));

resolver.setAuthorizedAttester(authorizedAttester, false);
assertFalse(resolver.isAuthorizedAttester(authorizedAttester));

bytes32[] memory linkedBadges = new bytes32[](1);
linkedBadges[0] = bytes32("Solidity");

AttestationRequest memory request = AttestationRequest({
schema: schemaId,
data: AttestationRequestData({
recipient: recipient,
expirationTime: 0,
revocable: true,
refUID: bytes32(0),
data: abi.encode("Solidity Dev", linkedBadges),
value: 0
})
});

vm.prank(authorizedAttester);
vm.expectRevert();
eas.attest(request);
}
}
Loading