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

feat: make resolver upgradable #36

Merged
merged 4 commits into from
Apr 10, 2024
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
6 changes: 3 additions & 3 deletions examples/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ RPC_ENDPOINT='http://127.0.0.1:8545'

EAS_MAIN_CONTRACT_ADDRESS='0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512'

SCROLL_BADGE_SCHEMA_UID='0xd2c8c891990e52369c94dac301cd6090c885606457fd489ed59343402b1aa7e5'
SCROLL_BADGE_SCHEMA_UID='0x81b69c8f7b364e9f7d8be9c19525df9ec003487dcd39ef647cb1a2f7a241bc08'
SCROLL_BADGE_SCHEMA='address badge, bytes payload'

SIMPLE_BADGE_CONTRACT_ADDRESS='0x610178dA211FEF7D417bC0e6FeD39F05609AD788'
SIMPLE_BADGE_ATTESTER_PROXY_CONTRACT_ADDRESS='0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e'
SIMPLE_BADGE_CONTRACT_ADDRESS='0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0'
SIMPLE_BADGE_ATTESTER_PROXY_CONTRACT_ADDRESS='0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82'

SCROLL_PROFILE_REGISTRY_PROXY_CONTRACT_ADDRESS='0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9'

Expand Down
2 changes: 1 addition & 1 deletion examples/src/attest-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const provider = new ethers.JsonRpcProvider(process.env.RPC_ENDPOINT);
const signer = (new ethers.Wallet(process.env.SIGNER_PRIVATE_KEY)).connect(provider);

// example query:
// curl 'localhost:3000/api/badge/0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9/claim?recipient=0x0000000000000000000000000000000000000001'
// curl 'localhost:3000/api/badge/0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0/claim?recipient=0x0000000000000000000000000000000000000001'
app.get('/api/badge/:address/claim', async (req, res) => {
const { recipient } = req.query;
const { address } = req.params;
Expand Down
3 changes: 2 additions & 1 deletion examples/src/attest-simple.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { createBadge } from './lib.js';
import 'dotenv/config';

const abi = [
'error SingletonBadge()'
'error SingletonBadge()',
'error Unauthorized()'
]

async function main() {
Expand Down
2 changes: 1 addition & 1 deletion examples/test-env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ export SIGNER_ADDRESS=$(cast wallet address "$SIGNER_PRIVATE_KEY")
export TREASURY_ADDRESS=$(cast wallet address "$SIGNER_PRIVATE_KEY")

pushd ..
forge script script/DeployTestContracts.sol:DeployTestContracts --rpc-url http://localhost:8545 --broadcast 2>&1
forge script script/DeployTestContracts.sol:DeployTestContracts --rpc-url http://127.0.0.1:8545 --broadcast 2>&1

fg
82 changes: 82 additions & 0 deletions script/DeployCanvasContracts.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;

import {Script} from "forge-std/Script.sol";
import {console} from "forge-std/console.sol";

import {EAS} from "@eas/contracts/EAS.sol";

import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";

import {
ITransparentUpgradeableProxy,
TransparentUpgradeableProxy
} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";

import {EmptyContract} from "../src/misc/EmptyContract.sol";
import {Profile} from "../src/profile/Profile.sol";
import {ProfileRegistry} from "../src/profile/ProfileRegistry.sol";
import {ScrollBadgeResolver} from "../src/resolver/ScrollBadgeResolver.sol";

contract DeployCanvasContracts is Script {
uint256 DEPLOYER_PRIVATE_KEY = vm.envUint("DEPLOYER_PRIVATE_KEY");

address SIGNER_ADDRESS = vm.envAddress("SIGNER_ADDRESS");
address TREASURY_ADDRESS = vm.envAddress("TREASURY_ADDRESS");

address EAS_ADDRESS = vm.envAddress("EAS_ADDRESS");

function run() external {
vm.startBroadcast(DEPLOYER_PRIVATE_KEY);

// deploy proxy admin
ProxyAdmin proxyAdmin = new ProxyAdmin();

// deploy profile registry placeholder
address placeholder = address(new EmptyContract());
address profileRegistryProxy = address(new TransparentUpgradeableProxy(placeholder, address(proxyAdmin), ""));

// deploy Scroll badge resolver
address resolverImpl = address(new ScrollBadgeResolver(EAS_ADDRESS, profileRegistryProxy));
address resolverProxy = address(new TransparentUpgradeableProxy(resolverImpl, address(proxyAdmin), ""));
ScrollBadgeResolver resolver = ScrollBadgeResolver(payable(resolverProxy));
resolver.initialize();

bytes32 schema = resolver.schema();

// deploy profile implementation and upgrade registry
Profile profileImpl = new Profile(address(resolver));
ProfileRegistry profileRegistryImpl = new ProfileRegistry();
proxyAdmin.upgrade(ITransparentUpgradeableProxy(profileRegistryProxy), address(profileRegistryImpl));
ProfileRegistry(profileRegistryProxy).initialize(TREASURY_ADDRESS, SIGNER_ADDRESS, address(profileImpl));

// misc
bytes32[] memory blacklist = new bytes32[](1);
blacklist[0] = keccak256(bytes("vpn"));
ProfileRegistry(profileRegistryProxy).blacklistUsername(blacklist);

ProfileRegistry(profileRegistryProxy).updateSigner(0x70997970C51812dc3A010C7d01b50e0d17dc79C8);

// log addresses
logAddress("DEPLOYER_ADDRESS", vm.addr(DEPLOYER_PRIVATE_KEY));
logAddress("SIGNER_ADDRESS", SIGNER_ADDRESS);
logAddress("TREASURY_ADDRESS", TREASURY_ADDRESS);
logAddress("EAS_ADDRESS", EAS_ADDRESS);
logAddress("SCROLL_PROFILE_REGISTRY_PROXY_ADMIN_ADDRESS", address(proxyAdmin));
logAddress("SCROLL_PROFILE_REGISTRY_PROXY_CONTRACT_ADDRESS", address(profileRegistryProxy));
logAddress("SCROLL_BADGE_RESOLVER_CONTRACT_ADDRESS", address(resolver));
logBytes32("SCROLL_BADGE_SCHEMA_UID", schema);
logAddress("SCROLL_PROFILE_IMPLEMENTATION_CONTRACT_ADDRESS", address(profileImpl));
logAddress("SCROLL_PROFILE_REGISTRY_IMPLEMENTATION_CONTRACT_ADDRESS", address(profileRegistryImpl));

vm.stopBroadcast();
}

function logAddress(string memory name, address addr) internal view {
console.log(string(abi.encodePacked(name, "=", vm.toString(address(addr)))));
}

function logBytes32(string memory name, bytes32 data) internal view {
console.log(string(abi.encodePacked(name, "=", vm.toString(data))));
}
}
99 changes: 99 additions & 0 deletions script/DeployCanvasTestBadgeContracts.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;

import {Script} from "forge-std/Script.sol";
import {console} from "forge-std/console.sol";

import {Attestation} from "@eas/contracts/IEAS.sol";

import {ScrollBadge} from "../src/badge/ScrollBadge.sol";
import {EthereumYearBadge} from "../src/badge/examples/EthereumYearBadge.sol";
import {ScrollBadgeTokenOwner} from "../src/badge/examples/ScrollBadgeTokenOwner.sol";
import {ScrollBadgeSelfAttest} from "../src/badge/extensions/ScrollBadgeSelfAttest.sol";
import {ScrollBadgeSingleton} from "../src/badge/extensions/ScrollBadgeSingleton.sol";
import {ScrollBadgeResolver} from "../src/resolver/ScrollBadgeResolver.sol";

contract CanvasTestBadge is ScrollBadgeSelfAttest, ScrollBadgeSingleton {
string public sharedTokenURI;

constructor(address resolver_, string memory tokenUri_) ScrollBadge(resolver_) {
sharedTokenURI = tokenUri_;
}

function onIssueBadge(Attestation calldata attestation)
internal
virtual
override (ScrollBadgeSelfAttest, ScrollBadgeSingleton)
returns (bool)
{
return super.onIssueBadge(attestation);
}

function onRevokeBadge(Attestation calldata attestation)
internal
virtual
override (ScrollBadgeSelfAttest, ScrollBadgeSingleton)
returns (bool)
{
return super.onRevokeBadge(attestation);
}

function badgeTokenURI(bytes32 /*uid*/ ) public view override returns (string memory) {
return sharedTokenURI;
}
}

contract DeployCanvasTestBadgeContracts is Script {
uint256 DEPLOYER_PRIVATE_KEY = vm.envUint("DEPLOYER_PRIVATE_KEY");

address RESOLVER_ADDRESS = vm.envAddress("SCROLL_BADGE_RESOLVER_CONTRACT_ADDRESS");

function run() external {
vm.startBroadcast(DEPLOYER_PRIVATE_KEY);

ScrollBadgeResolver resolver = ScrollBadgeResolver(payable(RESOLVER_ADDRESS));

// deploy test badges
CanvasTestBadge badge1 = new CanvasTestBadge(
address(resolver), "ipfs://bafybeibc5sgo2plmjkq2tzmhrn54bk3crhnc23zd2msg4ea7a4pxrkgfna/1"
);

CanvasTestBadge badge2 = new CanvasTestBadge(
address(resolver), "ipfs://bafybeibc5sgo2plmjkq2tzmhrn54bk3crhnc23zd2msg4ea7a4pxrkgfna/2"
);

CanvasTestBadge badge3 = new CanvasTestBadge(
address(resolver), "ipfs://bafybeibc5sgo2plmjkq2tzmhrn54bk3crhnc23zd2msg4ea7a4pxrkgfna/3"
);

// deploy origins NFT badge
address[] memory tokens = new address[](1);
tokens[0] = 0xDd7d857F570B0C211abfe05cd914A85BefEC2464;

ScrollBadgeTokenOwner badge4 = new ScrollBadgeTokenOwner(address(resolver), tokens);

// deploy Ethereum year badge
EthereumYearBadge badge5 = new EthereumYearBadge(address(resolver), "https://nft.scroll.io/canvas/year/");

// set permissions
resolver.toggleBadge(address(badge1), true);
resolver.toggleBadge(address(badge2), true);
resolver.toggleBadge(address(badge3), true);
resolver.toggleBadge(address(badge4), true);
resolver.toggleBadge(address(badge5), true);

// log addresses
logAddress("DEPLOYER_ADDRESS", vm.addr(DEPLOYER_PRIVATE_KEY));
logAddress("SIMPLE_BADGE_A_CONTRACT_ADDRESS", address(badge1));
logAddress("SIMPLE_BADGE_B_CONTRACT_ADDRESS", address(badge2));
logAddress("SIMPLE_BADGE_C_CONTRACT_ADDRESS", address(badge3));
logAddress("ORIGINS_BADGE_ADDRESS", address(badge4));
logAddress("ETHEREUM_YEAR_BADGE_ADDRESS", address(badge5));

vm.stopBroadcast();
}

function logAddress(string memory name, address addr) internal view {
console.log(string(abi.encodePacked(name, "=", vm.toString(address(addr)))));
}
}
11 changes: 7 additions & 4 deletions script/DeployTestContracts.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,15 @@ contract DeployTestContracts is Script {
ProxyAdmin proxyAdmin = new ProxyAdmin();

// deploy profile registry placeholder
EmptyContract placeholder = new EmptyContract();
address profileRegistryProxy =
address(new TransparentUpgradeableProxy(address(placeholder), address(proxyAdmin), ""));
address placeholder = address(new EmptyContract());
address profileRegistryProxy = address(new TransparentUpgradeableProxy(placeholder, address(proxyAdmin), ""));

// deploy Scroll badge resolver
ScrollBadgeResolver resolver = new ScrollBadgeResolver(address(eas), profileRegistryProxy);
address resolverImpl = address(new ScrollBadgeResolver(address(eas), profileRegistryProxy));
address resolverProxy = address(new TransparentUpgradeableProxy(resolverImpl, address(proxyAdmin), ""));
ScrollBadgeResolver resolver = ScrollBadgeResolver(payable(resolverProxy));
resolver.initialize();

bytes32 schema = resolver.schema();

// deploy profile implementation and upgrade registry
Expand Down
2 changes: 1 addition & 1 deletion src/badge/examples/EthereumYearBadge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ contract EthereumYearBadge is
bytes memory payload = getPayload(attestation);
uint256 year = decodePayloadData(payload);

return string(abi.encodePacked(baseTokenURI, Strings.toString(year)));
return string(abi.encodePacked(baseTokenURI, Strings.toString(year), ".json"));
}

/// @inheritdoc ScrollBadgeCustomPayload
Expand Down
24 changes: 19 additions & 5 deletions src/resolver/ScrollBadgeResolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,19 @@ contract ScrollBadgeResolver is IScrollBadgeResolver, SchemaResolver, ScrollBadg
*/

/// @inheritdoc IScrollBadgeResolver
bytes32 public immutable schema;
address public immutable registry;

/**
*
* Variables *
*
*/

/// @inheritdoc IScrollBadgeResolver
address public immutable registry;
bytes32 public schema;

// Storage slots reserved for future upgrades.
uint256[49] private __gap;

/**
*
Expand All @@ -53,15 +62,20 @@ contract ScrollBadgeResolver is IScrollBadgeResolver, SchemaResolver, ScrollBadg
/// @param eas_ The address of the global EAS contract.
/// @param registry_ The address of the profile registry contract.
constructor(address eas_, address registry_) SchemaResolver(IEAS(eas_)) {
registry = registry_;
_disableInitializers();
}

function initialize() external initializer {
__Whitelist_init();

// register Scroll badge schema,
// we do this here to ensure that the resolver is correctly configured
schema = IEAS(eas_).getSchemaRegistry().register(
schema = _eas.getSchemaRegistry().register(
SCROLL_BADGE_SCHEMA,
ISchemaResolver(address(this)), // resolver
true // revocable
);

registry = registry_;
}

/**
Expand Down
35 changes: 32 additions & 3 deletions src/resolver/ScrollBadgeResolverWhitelist.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,44 @@

pragma solidity 0.8.19;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

abstract contract ScrollBadgeResolverWhitelist is OwnableUpgradeable {
/**
*
* Variables *
*
*/

abstract contract ScrollBadgeResolverWhitelist is Ownable {
// If false, all badges are allowed.
bool public whitelistEnabled = true;
bool public whitelistEnabled;

// Authorized badge contracts.
mapping(address => bool) public whitelist;

// Storage slots reserved for future upgrades.
uint256[48] private __gap;

/**
*
* Constructor *
*
*/
constructor() {
_disableInitializers();
}

function __Whitelist_init() internal onlyInitializing {
__Ownable_init();
whitelistEnabled = true;
}

/**
*
* Restricted Functions *
*
*/

/// @notice Enables or disables a given badge contract.
/// @param badge The badge address.
/// @param enable True if enable, false if disable.
Expand Down
44 changes: 44 additions & 0 deletions test/EthereumYearBadge.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

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

import {EMPTY_UID, NO_EXPIRATION_TIME} from "@eas/contracts/Common.sol";
import {AttestationRequest, AttestationRequestData} from "@eas/contracts/IEAS.sol";

import {EthereumYearBadge} from "../src/badge/examples/EthereumYearBadge.sol";

contract EthereumYearBadgeTest is ScrollBadgeTestBase {
EthereumYearBadge internal badge;

string baseTokenURI = "http://scroll-canvas.io/";

function setUp() public virtual override {
super.setUp();

badge = new EthereumYearBadge(address(resolver), baseTokenURI);
resolver.toggleBadge(address(badge), true);
badge.toggleAttester(address(this), true);
}

function testAttestOnce(address recipient) external {
bytes memory payload = abi.encode(2024);
bytes memory attestationData = abi.encode(badge, payload);

AttestationRequestData memory _attData = AttestationRequestData({
recipient: recipient,
expirationTime: NO_EXPIRATION_TIME,
revocable: false,
refUID: EMPTY_UID,
data: attestationData,
value: 0
});

AttestationRequest memory _req = AttestationRequest({schema: schema, data: _attData});
bytes32 uid = eas.attest(_req);

string memory uri = badge.badgeTokenURI(uid);
assertEq(uri, "http://scroll-canvas.io/2024.json");
}
}
Loading
Loading