Skip to content

Commit

Permalink
feat: make resolver upgradable (#36)
Browse files Browse the repository at this point in the history
* feat: make resolver upgradable

* add sepolia deploy scripts

* deploy EthereumYear badge

* add .json suffix
  • Loading branch information
Thegaram authored Apr 10, 2024
1 parent 5925f61 commit 111a0c0
Show file tree
Hide file tree
Showing 14 changed files with 312 additions and 22 deletions.
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

0 comments on commit 111a0c0

Please sign in to comment.