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

Add smart contract for checking passport status (#72) #77

Merged
merged 9 commits into from
Oct 22, 2023
13 changes: 13 additions & 0 deletions contracts/governance/IVotingEscrow.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;

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

interface IVotingEscrow is IERC20 {
struct LockedBalance {
int128 amount;
uint256 end;
}

function locked(address) external view returns (LockedBalance memory);
}
10 changes: 10 additions & 0 deletions contracts/mock/PassportIssuerMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;

import "../passport/IPassportIssuer.sol";

contract PassportIssuerMock is IPassportIssuer {
function revokeUnderBalance() public view returns (uint256) {
return 1.5 * 1e18;
}
}
31 changes: 31 additions & 0 deletions contracts/mock/VotingEscrowMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;

import "../governance/IVotingEscrow.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "hardhat/console.sol";

contract VotingEscrowMock is ERC20, IVotingEscrow {
mapping(address => LockedBalance) locks;

Check warning on line 9 in contracts/mock/VotingEscrowMock.sol

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 16.x)

Explicitly mark visibility of state

Check warning on line 9 in contracts/mock/VotingEscrowMock.sol

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 18.x)

Explicitly mark visibility of state

constructor() ERC20("Vote-escrowed NATION", "veNATION") {
_mint(msg.sender, 100 * 1e18);
}

function create_lock(int128 _value, uint256 _unlock_time) public {

Check warning on line 15 in contracts/mock/VotingEscrowMock.sol

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 16.x)

Function name must be in mixedCase

Check warning on line 15 in contracts/mock/VotingEscrowMock.sol

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 16.x)

Variable name must be in mixedCase

Check warning on line 15 in contracts/mock/VotingEscrowMock.sol

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 18.x)

Function name must be in mixedCase

Check warning on line 15 in contracts/mock/VotingEscrowMock.sol

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 18.x)

Variable name must be in mixedCase
console.log("create_lock");
console.log("uint256(int256(_value)):", uint256(int256(_value)));
console.log("_unlock_time:", _unlock_time);
LockedBalance memory lockedBalance = LockedBalance({
amount: _value,
end: _unlock_time
});
locks[msg.sender] = lockedBalance;
}

function locked(
address account
) public view returns (LockedBalance memory lockedBalance) {
return locks[account];
}
}
6 changes: 6 additions & 0 deletions contracts/passport/IPassportIssuer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;

interface IPassportIssuer {
function revokeUnderBalance() external view returns (uint256);
}
33 changes: 33 additions & 0 deletions contracts/utils/IPassportUtils.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;

interface IPassportUtils {
/**
* Returns `true` if a citizen's passport has become revocable.
*
* @param citizen The address of an NFT passport's owner
*/
function isExpired(address citizen) external view returns (bool);

/**
* Returns the Unix epoch time when a citizen's passport will become revocable.
*
* @param citizen The address of an NFT passport's owner
*/
function getExpirationTimestamp(
address citizen
) external view returns (uint256);

/**
* Calculates the Unix epoch time when vote-escrowed `$NATION` will drop below a given threshold.
*
* @param lockAmount The amount of `$NATION` tokens that were locked
* @param lockEnd The lock expiration date in seconds
* @param votingEscrowThreshold The vote-escrowed `$NATION` balance when a passport will become revocable
*/
function calculateThresholdTimestamp(
uint256 lockAmount,
uint256 lockEnd,
uint256 votingEscrowThreshold
) external view returns (uint256);
}
80 changes: 80 additions & 0 deletions contracts/utils/PassportUtils.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;

import "./IPassportUtils.sol";
import "../passport/IPassportIssuer.sol";
import "../governance/IVotingEscrow.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "hardhat/console.sol";

contract PassportUtils is IPassportUtils {
using SafeERC20 for IERC20;

IPassportIssuer public passportIssuer;
IVotingEscrow public votingEscrow;

constructor(address passportIssuerAddress, address votingEscrowAddress) {
passportIssuer = IPassportIssuer(passportIssuerAddress);
votingEscrow = IVotingEscrow(votingEscrowAddress);
}

function isExpired(address citizen) public view returns (bool) {
uint256 revokeUnderBalance = passportIssuer.revokeUnderBalance();
console.log("revokeUnderBalance:", revokeUnderBalance);
uint256 votingEscrowBalance = votingEscrow.balanceOf(citizen);
console.log("votingEscrowBalance:", votingEscrowBalance);
return votingEscrowBalance < revokeUnderBalance;
}

function getExpirationTimestamp(
address citizen
) public view returns (uint256) {
IVotingEscrow.LockedBalance memory lockedBalance = votingEscrow.locked(
citizen
);
uint256 lockAmount = uint256(int256(lockedBalance.amount));
console.log("lockAmount:", lockAmount);
uint256 lockEnd = lockedBalance.end;
console.log("lockEnd:", lockEnd);
uint256 revokeUnderBalance = passportIssuer.revokeUnderBalance();
console.log("revokeUnderBalance:", revokeUnderBalance);
return
calculateThresholdTimestamp(
lockAmount,
lockEnd,
revokeUnderBalance
);
}

function calculateThresholdTimestamp(
uint256 lockAmount,
uint256 lockEnd,
uint256 votingEscrowThreshold
) public view returns (uint256) {
console.log("lockAmount:", lockAmount);
console.log("lockEnd:", lockEnd);
console.log("votingEscrowThreshold:", votingEscrowThreshold);

uint256 maxLockPeriod = 4 * 365 days;
console.log("maxLockPeriod:", maxLockPeriod);
console.log("block.timestamp:", block.timestamp);

Check warning on line 60 in contracts/utils/PassportUtils.sol

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 16.x)

Avoid to make time-based decisions in your business logic

Check warning on line 60 in contracts/utils/PassportUtils.sol

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 18.x)

Avoid to make time-based decisions in your business logic
uint256 secondsUntilUnlock = lockEnd - block.timestamp;

Check warning on line 61 in contracts/utils/PassportUtils.sol

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 16.x)

Avoid to make time-based decisions in your business logic

Check warning on line 61 in contracts/utils/PassportUtils.sol

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 18.x)

Avoid to make time-based decisions in your business logic
console.log("secondsUntilUnlock:", secondsUntilUnlock);

uint256 thresholdPercentageOfLocked = (100 ether *
votingEscrowThreshold) / lockAmount;
console.log(
"thresholdPercentageOfLocked:",
thresholdPercentageOfLocked
);

uint256 secondsFromThresholdToUnlock = (maxLockPeriod *
thresholdPercentageOfLocked) / 100 ether;
console.log(
"secondsFromThresholdToUnlock:",
secondsFromThresholdToUnlock
);

return lockEnd - secondsFromThresholdToUnlock;
}
}
4 changes: 2 additions & 2 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
@@ -1,6 +1,6 @@
{
"name": "nationcred-contracts",
"version": "1.0.0",
"version": "0.5.0",
"description": "NationCred smart contracts",
"main": "index.js",
"scripts": {
Expand Down
35 changes: 35 additions & 0 deletions scripts/deploy-passport-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// We require the Hardhat Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
//
// When running the script with `npx hardhat run <script>` you'll find the Hardhat
// Runtime Environment's members available in the global scope.
import { ethers } from "hardhat";

async function main() {
// Hardhat always runs the compile task when running scripts with its command
// line interface.
//
// If this script is run directly using `node` you may want to call compile
// manually to make sure everything is compiled
// await hre.run('compile');

// We get the contract to deploy
const PassportUtils = await ethers.getContractFactory("PassportUtils");
const passportIssuerAddress = "0x8c16926819AB30B8b29A8E23F5C230d164337093";
const votingEscrowAddress = "0xF7deF1D2FBDA6B74beE7452fdf7894Da9201065d";
const passportUtils = await PassportUtils.deploy(
passportIssuerAddress,
votingEscrowAddress
);

await passportUtils.deployed();

console.log("PassportUtils deployed to:", passportUtils.address);
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Loading
Loading