Skip to content

Commit

Permalink
Merge pull request #15 from charmverse/resolver-contract
Browse files Browse the repository at this point in the history
add example contracts for scout game
  • Loading branch information
mattcasey authored Oct 23, 2024
2 parents 1081b5d + 53149c8 commit 4feecff
Show file tree
Hide file tree
Showing 6 changed files with 322 additions and 15 deletions.
31 changes: 21 additions & 10 deletions contracts/README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
# Builder NFT Contracts
# Contracts

## contracts/BuilderNFTSeasonOneUpgradeable.sol
## ScoutToken.sol

ERC20 tokens for purchasing Builder NFT

# Testing
We use hardhat/viem and jest for our unit tests.
## StargateVesting.sol

Contract to enable the Stargate Protocol to receive funds on a schedule.

## StargateProtocol.sol

Allows players to claim their points. Receives tokens and EAS attestations on a weekly basis which contain information how to distribute funds. See: [EAS Resolver Contract](https://docs.attest.org/docs/core--concepts/resolver-contracts).

## BuilderNFTSeasonOneUpgradeable.sol

ERC1155 tokens for builders

## Lock.sol

Simple locking contract that we use to set up simple set of working jest tests

## USDC Contracts

We use the official USDC contracts from Optimism so that our unit tests are accurately using the underlying contract.

USDC Contracts valid as of October 16th 2024

### contracts/FiatTokenProxy
Proxy for USDC
https://optimistic.etherscan.io/token/0x0b2c639c533813f4aa9d7837caf62653d097ff85#code
### FiatTokenProxy

Proxy for [USDC](https://optimistic.etherscan.io/token/0x0b2c639c533813f4aa9d7837caf62653d097ff85#code)

### contracts/FiatTokenV2_2
Implementation for USDC
https://optimistic.etherscan.io/address/0xdEd3b9a8DBeDC2F9CB725B55d0E686A81E6d06dC#code
### FiatTokenV2_2

Implementation for [USDC](https://optimistic.etherscan.io/address/0xdEd3b9a8DBeDC2F9CB725B55d0E686A81E6d06dC#code)
85 changes: 85 additions & 0 deletions contracts/ScoutGameProtocol.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.26;

import "@ethereum-attestation-service/eas-contracts/contracts/resolver/SchemaResolver.sol";
import "@ethereum-attestation-service/eas-contracts/contracts/IEAS.sol";
import "./ScoutToken.sol";

/// @title ScoutGameProtocol
/// @notice A schema resolver that manages unclaimed balances based on EAS attestations.
contract ScoutGameProtocol is SchemaResolver {
ScoutToken public token;
mapping(address => uint256) private _unclaimedBalances;

// Define a constant for 18 decimals
uint256 constant DECIMALS = 10 ** 18;

constructor(IEAS eas, address _token) SchemaResolver(eas) {
token = ScoutToken(_token);
}

// Method that is called by the EAS contract when an attestation is made
//
function onAttest(
Attestation calldata attestation,
uint256 /*value*/
) internal override returns (bool) {
uint256 addedBalance = 10 * DECIMALS;
_unclaimedBalances[attestation.recipient] += addedBalance;
return true;
}

// Method that is called by the EAS contract when an attestation is revoked
function onRevoke(
Attestation calldata attestation,
uint256 /*value*/
) internal pure override returns (bool) {
return true;
}

function getUnclaimedBalance(
address account
) public view returns (uint256) {
return _unclaimedBalances[account];
}

function getTokenBalance(address account) public view returns (uint256) {
return token.balanceOf(account);
}

// Allow the sender to claim their balance as ERC20 tokens
function claimBalance(uint256 amount) public returns (bool) {
require(
_unclaimedBalances[msg.sender] >= amount,
"Insufficient unclaimed balance"
);
uint256 contractHolding = token.balanceOf(address(this));
require(contractHolding >= amount, "Insufficient balance in contract");

_unclaimedBalances[msg.sender] -= amount;
token.transfer(msg.sender, amount);
return true;
}

// Deposit funds to the contract
function depositFunds(uint256 amount) public {
token.transferFrom(msg.sender, address(this), amount);
}

function decodeValue(
bytes memory attestationData
) internal pure returns (uint256) {
uint256 value;

// Decode the attestation data
assembly {
// Skip the length field of the byte array
attestationData := add(attestationData, 0x20)

// Read the value (32 bytes)
value := mload(add(attestationData, 0x00))
}
return value;
}
}
61 changes: 61 additions & 0 deletions contracts/ScoutToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.26;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "@openzeppelin/contracts/access/extensions/AccessControlEnumerable.sol";
import "@openzeppelin/contracts/utils/Context.sol";

contract ScoutToken is Context, AccessControlEnumerable, ERC20Pausable {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bytes32 public constant VESTING_ROLE = keccak256("VESTING_ROLE");

constructor(string memory name, string memory symbol) ERC20(name, symbol) {
_grantRole(DEFAULT_ADMIN_ROLE, _msgSender());

_grantRole(MINTER_ROLE, _msgSender());
_grantRole(PAUSER_ROLE, _msgSender());
}

function mint(address to, uint256 amount) public virtual {
require(
hasRole(MINTER_ROLE, _msgSender()),
"Must have minter role to mint"
);
_mint(to, amount);
}

function mintForVesting(address to, uint256 amount) public virtual {
require(
hasRole(VESTING_ROLE, _msgSender()),
"Must have vesting role to mint for vesting"
);
_mint(to, amount);
}

function pause() public virtual {
require(
hasRole(PAUSER_ROLE, _msgSender()),
"Must have pauser role to pause"
);
_pause();
}

function unpause() public virtual {
require(
hasRole(PAUSER_ROLE, _msgSender()),
"Must have pauser role to unpause"
);
_unpause();
}

function grantVestingRole(address vestingContract) public virtual {
require(
hasRole(DEFAULT_ADMIN_ROLE, _msgSender()),
"Must have admin role to grant vesting role"
);
_grantRole(VESTING_ROLE, vestingContract);
}
}
150 changes: 150 additions & 0 deletions contracts/ScoutVesting.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.26;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract ScoutVesting is Ownable {
IERC20 public scoutToken;
address public scoutGameProtocol;

struct VestingSchedule {
uint256 totalAmount;
uint256 startTime;
uint256 duration;
uint256 releasedAmount;
}

mapping(address => VestingSchedule) public vestingSchedules;
address[] public employees;
VestingSchedule public protocolVestingSchedule;

event TokensVested(address indexed beneficiary, uint256 amount);
event ProtocolVestingScheduleAdded(
uint256 totalAmount,
uint256 startTime,
uint256 duration
);

constructor(
address _scoutToken,
address _scoutGameProtocol
) Ownable(msg.sender) {
scoutToken = IERC20(_scoutToken);
scoutGameProtocol = _scoutGameProtocol;
}

function addEmployee(
address employee,
uint256 totalAmount,
uint256 startTime,
uint256 duration
) external onlyOwner {
require(
vestingSchedules[employee].totalAmount == 0,
"Employee already exists"
);

vestingSchedules[employee] = VestingSchedule({
totalAmount: totalAmount,
startTime: startTime,
duration: duration,
releasedAmount: 0
});

employees.push(employee);
}

function addProtocolVestingSchedule(
uint256 totalAmount,
uint256 startTime,
uint256 duration
) external onlyOwner {
require(
protocolVestingSchedule.totalAmount == 0,
"Protocol vesting schedule already exists"
);

protocolVestingSchedule = VestingSchedule({
totalAmount: totalAmount,
startTime: startTime,
duration: duration,
releasedAmount: 0
});

emit ProtocolVestingScheduleAdded(totalAmount, startTime, duration);
}

function vest() external {
VestingSchedule storage schedule = vestingSchedules[msg.sender];
require(schedule.totalAmount > 0, "No vesting schedule found");

uint256 vestedAmount = calculateVestedAmount(schedule);
uint256 claimableAmount = vestedAmount - schedule.releasedAmount;

require(claimableAmount > 0, "No tokens available for vesting");

schedule.releasedAmount += claimableAmount;
require(
scoutToken.transfer(msg.sender, claimableAmount),
"Token transfer failed"
);

emit TokensVested(msg.sender, claimableAmount);
}

function vestForProtocol() external {
require(
msg.sender == scoutGameProtocol,
"Only ScoutGameProtocol can call this function"
);
require(
protocolVestingSchedule.totalAmount > 0,
"No protocol vesting schedule found"
);

uint256 vestedAmount = calculateVestedAmount(protocolVestingSchedule);
uint256 claimableAmount = vestedAmount -
protocolVestingSchedule.releasedAmount;

require(claimableAmount > 0, "No tokens available for vesting");

protocolVestingSchedule.releasedAmount += claimableAmount;
require(
scoutToken.transfer(scoutGameProtocol, claimableAmount),
"Token transfer failed"
);

emit TokensVested(scoutGameProtocol, claimableAmount);
}

function calculateVestedAmount(
VestingSchedule memory schedule
) internal view returns (uint256) {
if (block.timestamp < schedule.startTime) {
return 0;
} else if (block.timestamp >= schedule.startTime + schedule.duration) {
return schedule.totalAmount;
} else {
return
(schedule.totalAmount *
(block.timestamp - schedule.startTime)) / schedule.duration;
}
}

function getVestedAmount(address employee) external view returns (uint256) {
VestingSchedule memory schedule = vestingSchedules[employee];
return calculateVestedAmount(schedule);
}

function getProtocolVestedAmount() external view returns (uint256) {
return calculateVestedAmount(protocolVestingSchedule);
}

function updateScoutGameProtocol(
address _newScoutGameProtocol
) external onlyOwner {
scoutGameProtocol = _newScoutGameProtocol;
}
}
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"@nomicfoundation/hardhat-ethers": "^3.0.6",
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
"@nomicfoundation/hardhat-viem": "^2.0.3",
"@openzeppelin/contracts": "^5.0.2",
"@openzeppelin/contracts": "^5.1.0",
"@openzeppelin/contracts-upgradeable": "^5.0.2",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^7.2.0",
Expand Down

0 comments on commit 4feecff

Please sign in to comment.