-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #15 from charmverse/resolver-contract
add example contracts for scout game
- Loading branch information
Showing
6 changed files
with
322 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters