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

Protocol testnet #16

Merged
merged 38 commits into from
Nov 12, 2024
Merged

Protocol testnet #16

merged 38 commits into from
Nov 12, 2024

Conversation

motechFR
Copy link
Contributor

@motechFR motechFR commented Nov 1, 2024

No description provided.

Comment on lines +12 to +17
describe('returns', function () {
let testMemoryUtils: Awaited<ReturnType<typeof deployTestMemoryUtils>>['testMemoryUtilsContract'];

beforeAll(async () => {
testMemoryUtils = (await deployTestMemoryUtils()).testMemoryUtilsContract;
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test suite is critical to ensure future updates do not change memory locations.

It ensures we don't lock ourselves out of an admin slot for example

@@ -7,17 +7,21 @@ import { generateWallets } from './generateWallets';
export async function deployBuilderNftContract({ USDCContractAddress }: { USDCContractAddress?: Address } = {}) {
const { adminAccount: admin, thirdUserAccount: proceedsReceiverAccount } = await generateWallets();

const implementation = await viem.deployContract('BuilderNFTSeasonOneImplementation01');
const implementation = await viem.deployContract('BuilderNFTSeasonOneImplementation01', [], {
client: { wallet: admin }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed deploys to use a specific wallet as the client, and not use default viem data

import type { GeneratedWallet } from './generateWallets';
import { generateWallets } from './generateWallets';

export async function deployEASContracts() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generates EAS Resolver, and deploys both our schemas


const decimalMultiplier = 10n ** decimals;

async function mintTo({ account, amount }: { account: string; amount: number }) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Utility methods for using the ERC20 more easily

initialEthBalance?: number;
};

export async function generateWallets({ initialEthBalance = 1 }: GeneratedWalletConfig = { initialEthBalance: 1 }) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I discovered we weren't initialising contracts with wallets, but the default wallet with viem, which is why it automagically worked.

this fixes generate wallets so we for sure always get different wallets

@@ -25,9 +25,9 @@ import { IERC1271 } from "../interface/IERC1271.sol";
* @dev Signature verification helper that can be used instead of `ECRecover.recover` to seamlessly support both ECDSA
* signatures from externally owned accounts (EOAs) as well as ERC1271 signatures from smart contract wallets.
*
* Adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/21bb89ef5bfc789b9333eb05e3ba2b7b284ac77c/contracts/utils/cryptography/SignatureChecker.sol
* Adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/21bb89ef5bfc789b9333eb05e3ba2b7b284ac77c/contracts/utils/cryptography/SignatureCheckerFiatProxy.sol
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was a conflict with another SignatureChecker from EAS libs, so renamed this to avoid issues (This one compiles with 0.6.x whereas EAS compiles with 0.8.27)


import "@openzeppelin/contracts/utils/StorageSlot.sol";

library MemoryUtils {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a single core memory library with namespacing. This makes it easier to reason about every storage slot used across our contracts

import "../libs/ProtocolAccessControl.sol";
import "../libs/MemoryUtils.sol";

contract TestMemoryUtils {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Memory Utils has everything internal

This allows us to access those internals and make them testable

Comment on lines +72 to +82
function _setRole(bytes32 role, address account) internal {
require(account != address(0), "Invalid account. Cannot be empty");

address _currentHolder = _roleHolder(role);

// Update the role only if it's different
if (_currentHolder != account) {
MemoryUtils._setAddress(role, account);
emit RoleTransferred(MemoryUtils._getRoleName(role), _currentHolder, account);
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Standard behaviour for granting roles.

RoleTransferred provides inbuilt auditability for all our role changes

Comment on lines +24 to +26
scoutgameEASResolver?: Address | null;
scoutgameErc20TokenDev?: Address | null;
scoutgameScoutProtocolProxyDev?: Address | null;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the 3 main keys

I've been considering refactoring these under a dev | prod key with same type to avoid duplication of keys

@@ -1,41 +1,48 @@
import dotenv from 'dotenv';
import { execSync } from 'node:child_process';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ESLint across this file

const easRegistryContract = await viem.getContractAt('ISchemaRegistry', easSchemaRegistryAddress);

console.log('Deploying the resolver contract...');
const deployedResolver = await viem.deployContract(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaned up deploy script, viem has inbuilt encoding of deploy data and resolution of the ABI

No more need to path.resolve the ABI

process.env.PRIVATE_KEY?.startsWith('0x') ? process.env.PRIVATE_KEY : `0x${process.env.PRIVATE_KEY}`
) as `0x${string}`;

task('deployScoutGameERC20', 'Deploys or updates the BuilderNFTSeasonOne contracts').setAction(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: We should add an initialise() method or something similar in the ERC20 so that we don't have ability to mint new tokens from thin air

process.env.PRIVATE_KEY?.startsWith('0x') ? process.env.PRIVATE_KEY : `0x${process.env.PRIVATE_KEY}`
) as `0x${string}`;

task('deployScoutProtocol', 'Deploys or updates the ScoutProtocol contracts').setAction(async (taskArgs, hre) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as buildernft

Deploy a new implementation

Point a proxy to it, or also deploy a new proxy

import fs from 'fs';
import path from 'path';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ESLint

return true;
}

function setAttesterWallet(address _attesterWallet) external onlyAdmin {
Copy link
Contributor Author

@motechFR motechFR Nov 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Role slots defined in Memory utils

Setters for roles other than admin or pauser defined in Contract that uses this role

}

function setAttesterWallet(address _attesterWallet) external onlyAdmin {
_setRole(MemoryUtils.EAS_ATTESTER_SLOT, _attesterWallet);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is all we need to do.

EASResolver simply asserts access controls on the role assignment

We can then benefit from the standard method which has inbuilt event emissions


// Allow the sender to claim their balance as ERC20 tokens
function claim(
string memory week,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep the protocol simple by only giving it the notion of weeks

Seasons are managed by the caller

}

// Function to set the Merkle root for a given week
function setMerkleRoot(string memory week, bytes32 merkleRoot) external onlyAdminOrClaimManager onlyWhenNotPaused {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: We'll want to implement the claiming mechanism towards the vesting contract

We could do that here, but it then attaches this method and prevents us re-running manually

I suggest a claimScoutAllocation method defined separately


// Function to get the Merkle root hash for a given week
function getMerkleRoot(string memory week) public view returns (bytes32) {
bytes32 slot = keccak256(abi.encodePacked(week, MemoryUtils.MERKLE_ROOTS_SLOT));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably refactor this to a getWeekSlot() method

Also, need to double check how many slots are available in Solidity so we don't hit that max a few years down the line

import "./libs/ProtocolAccessControl.sol";
import "./libs/MemoryUtils.sol";

contract ScoutProtocolProxy is Context, ProtocolAccessControl {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super lightweight proxy

Attestation calldata attestation,
uint256 /*value*/
) internal override returns (bool) {
require(attestation.attester == attester() || attestation.attester == secondaryAttester(), "Invalid attester");
Copy link
Contributor Author

@motechFR motechFR Nov 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super lightweight validation on the attester only.

Since we control the attester, we can handle validation before it attest

ScoutTokenERC20 token = _getToken();

// Transfer tokens to the user
token.transfer(msg.sender, amount * (10 ** token.decimals()));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For easier reasoning, we pass the amount as integers across systems

We only need to wei-ify amounts in the contract

@motechFR motechFR merged commit 6e9c7e8 into main Nov 12, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant