- Complete MeridianToken standard with compliance & reserve checks - Multi-custodian ReserveAggregator supporting bank/crypto/fund admin - Comprehensive Compliance engine with KYC/AML/sanctions - Full interface definitions and deployment scripts - Test suite for core functionality - Ready for GBP launch with Anchorage custody integration
183 lines
8.0 KiB
JavaScript
183 lines
8.0 KiB
JavaScript
const { expect } = require("chai");
|
|
const { ethers } = require("hardhat");
|
|
|
|
describe("Meridian Protocol", function () {
|
|
let meridianToken, compliance, reserveAggregator;
|
|
let owner, addr1, addr2;
|
|
let custodianId;
|
|
|
|
beforeEach(async function () {
|
|
[owner, addr1, addr2] = await ethers.getSigners();
|
|
|
|
// Deploy Compliance
|
|
const Compliance = await ethers.getContractFactory("Compliance");
|
|
compliance = await Compliance.deploy(owner.address);
|
|
await compliance.deployed();
|
|
|
|
// Deploy ReserveAggregator
|
|
const ReserveAggregator = await ethers.getContractFactory("ReserveAggregator");
|
|
reserveAggregator = await ReserveAggregator.deploy(owner.address);
|
|
await reserveAggregator.deployed();
|
|
|
|
// Deploy MeridianToken
|
|
const MeridianToken = await ethers.getContractFactory("MeridianToken");
|
|
meridianToken = await MeridianToken.deploy(
|
|
"Meridian GBP",
|
|
"MGBP",
|
|
"GBP",
|
|
"Meridian LLC",
|
|
reserveAggregator.address,
|
|
compliance.address,
|
|
owner.address
|
|
);
|
|
await meridianToken.deployed();
|
|
|
|
// Add test custodian
|
|
custodianId = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("test-custodian"));
|
|
await reserveAggregator.addCustodian(
|
|
custodianId,
|
|
"Test Custodian",
|
|
owner.address, // Use owner as oracle for testing
|
|
3600, // 1 hour heartbeat
|
|
2 // fund admin type
|
|
);
|
|
});
|
|
|
|
describe("Deployment", function () {
|
|
it("Should deploy with correct parameters", async function () {
|
|
expect(await meridianToken.name()).to.equal("Meridian GBP");
|
|
expect(await meridianToken.symbol()).to.equal("MGBP");
|
|
expect(await meridianToken.currency()).to.equal("GBP");
|
|
expect(await meridianToken.issuerName()).to.equal("Meridian LLC");
|
|
expect(await meridianToken.minimumCollateralRatio()).to.equal(10200);
|
|
});
|
|
|
|
it("Should have correct access roles", async function () {
|
|
const MINTER_ROLE = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("MINTER_ROLE"));
|
|
expect(await meridianToken.hasRole(MINTER_ROLE, owner.address)).to.be.true;
|
|
});
|
|
});
|
|
|
|
describe("Compliance", function () {
|
|
it("Should whitelist addresses correctly", async function () {
|
|
await compliance.whitelistAddress(addr1.address, 2, "UK", 0);
|
|
expect(await compliance.isWhitelisted(addr1.address)).to.be.true;
|
|
expect(await compliance.isSanctioned(addr1.address)).to.be.false;
|
|
});
|
|
|
|
it("Should set velocity limits based on KYC level", async function () {
|
|
await compliance.whitelistAddress(addr1.address, 1, "UK", 0);
|
|
const limits = await compliance.getVelocityLimits(addr1.address);
|
|
expect(limits.dailyLimit).to.equal(ethers.utils.parseEther("1000"));
|
|
});
|
|
});
|
|
|
|
describe("Reserve Aggregator", function () {
|
|
it("Should add custodians correctly", async function () {
|
|
const custodianInfo = await reserveAggregator.getCustodianInfo(custodianId);
|
|
expect(custodianInfo.name).to.equal("Test Custodian");
|
|
expect(custodianInfo.isActive).to.be.true;
|
|
expect(custodianInfo.custodianType).to.equal(2);
|
|
});
|
|
|
|
it("Should attest reserves", async function () {
|
|
const reserveAmount = ethers.utils.parseEther("1020000");
|
|
const documentHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("reserve-doc"));
|
|
|
|
await reserveAggregator.attestReserves(
|
|
custodianId,
|
|
reserveAmount,
|
|
documentHash
|
|
);
|
|
|
|
const attestation = await reserveAggregator.getLatestAttestation(custodianId);
|
|
expect(attestation.balance).to.equal(reserveAmount);
|
|
expect(attestation.isValid).to.be.true;
|
|
});
|
|
});
|
|
|
|
describe("Token Operations", function () {
|
|
beforeEach(async function () {
|
|
// Whitelist recipient
|
|
await compliance.whitelistAddress(addr1.address, 2, "UK", 0);
|
|
|
|
// Attest reserves
|
|
const reserveAmount = ethers.utils.parseEther("1020000"); // £1.02M
|
|
const documentHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("reserve-doc"));
|
|
await reserveAggregator.attestReserves(custodianId, reserveAmount, documentHash);
|
|
});
|
|
|
|
it("Should mint tokens with sufficient reserves", async function () {
|
|
const mintAmount = ethers.utils.parseEther("1000000"); // £1M
|
|
const attestationHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("mint-attestation"));
|
|
|
|
await meridianToken.mint(addr1.address, mintAmount, attestationHash);
|
|
|
|
expect(await meridianToken.balanceOf(addr1.address)).to.equal(mintAmount);
|
|
expect(await meridianToken.totalSupply()).to.equal(mintAmount);
|
|
});
|
|
|
|
it("Should reject mint with insufficient reserves", async function () {
|
|
const mintAmount = ethers.utils.parseEther("1100000"); // £1.1M (exceeds 102% ratio)
|
|
const attestationHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("mint-attestation"));
|
|
|
|
await expect(
|
|
meridianToken.mint(addr1.address, mintAmount, attestationHash)
|
|
).to.be.revertedWith("Insufficient reserves for mint");
|
|
});
|
|
|
|
it("Should reject mint to non-whitelisted address", async function () {
|
|
const mintAmount = ethers.utils.parseEther("1000");
|
|
const attestationHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("mint-attestation"));
|
|
|
|
await expect(
|
|
meridianToken.mint(addr2.address, mintAmount, attestationHash)
|
|
).to.be.revertedWith("Recipient not whitelisted");
|
|
});
|
|
|
|
it("Should calculate collateralization ratio correctly", async function () {
|
|
const mintAmount = ethers.utils.parseEther("1000000");
|
|
const attestationHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("mint-attestation"));
|
|
|
|
await meridianToken.mint(addr1.address, mintAmount, attestationHash);
|
|
|
|
const [ratio, isValid] = await meridianToken.getCurrentCollateralizationRatio();
|
|
expect(isValid).to.be.true;
|
|
expect(ratio).to.equal(10200); // 102%
|
|
});
|
|
});
|
|
|
|
describe("Transfer Restrictions", function () {
|
|
beforeEach(async function () {
|
|
// Setup: mint tokens to addr1
|
|
await compliance.whitelistAddress(addr1.address, 2, "UK", 0);
|
|
await compliance.whitelistAddress(addr2.address, 2, "US", 0);
|
|
|
|
const reserveAmount = ethers.utils.parseEther("1020000");
|
|
const documentHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("reserve-doc"));
|
|
await reserveAggregator.attestReserves(custodianId, reserveAmount, documentHash);
|
|
|
|
const mintAmount = ethers.utils.parseEther("1000");
|
|
const attestationHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("mint-attestation"));
|
|
await meridianToken.mint(addr1.address, mintAmount, attestationHash);
|
|
});
|
|
|
|
it("Should allow transfer between whitelisted addresses", async function () {
|
|
const transferAmount = ethers.utils.parseEther("100");
|
|
|
|
await meridianToken.connect(addr1).transfer(addr2.address, transferAmount);
|
|
|
|
expect(await meridianToken.balanceOf(addr2.address)).to.equal(transferAmount);
|
|
});
|
|
|
|
it("Should reject transfer to non-whitelisted address", async function () {
|
|
const transferAmount = ethers.utils.parseEther("100");
|
|
const addr3 = ethers.Wallet.createRandom();
|
|
|
|
await expect(
|
|
meridianToken.connect(addr1).transfer(addr3.address, transferAmount)
|
|
).to.be.revertedWith("Recipient not whitelisted");
|
|
});
|
|
});
|
|
});
|