Skip to content

Commit

Permalink
Merge branch 'create-roles-race' into deploy-create-hat-race
Browse files Browse the repository at this point in the history
  • Loading branch information
DarksightKellar committed Oct 25, 2024
2 parents 7156102 + e314c7e commit 0f91f6c
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 37 deletions.
1 change: 1 addition & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"semi": true,
"arrowParens": "avoid",
"singleAttributePerLine": true,
"trailingComma": "all",
"overrides": [
{
"files": "*.sol",
Expand Down
5 changes: 5 additions & 0 deletions contracts/interfaces/hats/IHats.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,9 @@ interface IHats {
) external returns (bool success);

function transferHat(uint256 _hatId, address _from, address _to) external;

function isWearerOfHat(
address _user,
uint256 _hatId
) external view returns (bool isWearer);
}
25 changes: 22 additions & 3 deletions contracts/mocks/MockHats.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import {IHats} from "../interfaces/hats/IHats.sol";

contract MockHats is IHats {
uint256 public count = 0;
mapping(uint256 => address) hatWearers;

function mintTopHat(
address,
address _target,
string memory,
string memory
) external returns (uint256 topHatId) {
topHatId = count;
count++;
hatWearers[topHatId] = _target;
}

function createHat(
Expand All @@ -28,9 +30,26 @@ contract MockHats is IHats {
count++;
}

function mintHat(uint256, address) external pure returns (bool success) {
function mintHat(
uint256 hatId,
address wearer
) external returns (bool success) {
success = true;
hatWearers[hatId] = wearer;
}

function transferHat(uint256 _hatId, address _from, address _to) external {
require(
hatWearers[_hatId] == _from,
"MockHats: Invalid current wearer"
);
hatWearers[_hatId] = _to;
}

function transferHat(uint256, address, address) external {}
function isWearerOfHat(
address _user,
uint256 _hatId
) external view returns (bool isWearer) {
isWearer = hatWearers[_hatId] == _user;
}
}
91 changes: 57 additions & 34 deletions test/DecentHats_0_1_0.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe('DecentHats_0_1_0', () => {
hre.ethers.ZeroAddress,
0,
hre.ethers.ZeroAddress,
]
],
);

const saltNum = BigInt(`0x${Buffer.from(hre.ethers.randomBytes(32)).toString('hex')}`);
Expand All @@ -85,14 +85,14 @@ describe('DecentHats_0_1_0', () => {
createGnosisSetupCalldata,
saltNum,
gnosisSafeL2SingletonAddress,
gnosisSafeProxyFactory
gnosisSafeProxyFactory,
);
gnosisSafeAddress = predictedGnosisSafeAddress;

await gnosisSafeProxyFactory.createProxyWithNonce(
gnosisSafeL2SingletonAddress,
createGnosisSetupCalldata,
saltNum
saltNum,
);

gnosisSafe = GnosisSafeL2__factory.connect(predictedGnosisSafeAddress, deployer);
Expand All @@ -116,7 +116,7 @@ describe('DecentHats_0_1_0', () => {
to: gnosisSafeAddress,
transactionData: GnosisSafeL2__factory.createInterface().encodeFunctionData(
'enableModule',
[decentHatsAddress]
[decentHatsAddress],
),
signers: [dao],
});
Expand Down Expand Up @@ -178,7 +178,7 @@ describe('DecentHats_0_1_0', () => {
},
],
},
]
],
),
signers: [dao],
});
Expand Down Expand Up @@ -227,7 +227,7 @@ describe('DecentHats_0_1_0', () => {
},
hats: [],
},
]
],
),
signers: [dao],
});
Expand Down Expand Up @@ -259,7 +259,7 @@ describe('DecentHats_0_1_0', () => {
i,
erc6551Registry,
mockHatsAccountImplementationAddress,
mockHatsAddress
mockHatsAddress,
);

expect(await topHatAccount.tokenId()).eq(i);
Expand Down Expand Up @@ -331,7 +331,7 @@ describe('DecentHats_0_1_0', () => {
},
],
},
]
],
),
signers: [dao],
});
Expand All @@ -355,7 +355,7 @@ describe('DecentHats_0_1_0', () => {

it('Creates a Sablier stream for the hat with stream parameters', async () => {
const streamCreatedEvents = await mockSablier.queryFilter(
mockSablier.filters.StreamCreated()
mockSablier.filters.StreamCreated(),
);
expect(streamCreatedEvents.length).to.equal(1);

Expand All @@ -367,14 +367,14 @@ describe('DecentHats_0_1_0', () => {

it('Does not create a Sablier stream for hats without stream parameters', async () => {
const streamCreatedEvents = await mockSablier.queryFilter(
mockSablier.filters.StreamCreated()
mockSablier.filters.StreamCreated(),
);
expect(streamCreatedEvents.length).to.equal(1); // Only one stream should be created
});

it('Creates a Sablier stream with correct timestamps', async () => {
const streamCreatedEvents = await mockSablier.queryFilter(
mockSablier.filters.StreamCreated()
mockSablier.filters.StreamCreated(),
);
expect(streamCreatedEvents.length).to.equal(1);

Expand Down Expand Up @@ -453,15 +453,15 @@ describe('DecentHats_0_1_0', () => {
},
],
},
]
],
),
signers: [dao],
});
});

it('Creates multiple Sablier streams for a single hat', async () => {
const streamCreatedEvents = await mockSablier.queryFilter(
mockSablier.filters.StreamCreated()
mockSablier.filters.StreamCreated(),
);
expect(streamCreatedEvents.length).to.equal(2);

Expand All @@ -478,7 +478,7 @@ describe('DecentHats_0_1_0', () => {

it('Creates streams with correct parameters', async () => {
const streamCreatedEvents = await mockSablier.queryFilter(
mockSablier.filters.StreamCreated()
mockSablier.filters.StreamCreated(),
);

const stream1 = await mockSablier.getStream(streamCreatedEvents[0].args.streamId);
Expand All @@ -494,7 +494,7 @@ describe('DecentHats_0_1_0', () => {

it('Creates streams with correct timestamps', async () => {
const streamCreatedEvents = await mockSablier.queryFilter(
mockSablier.filters.StreamCreated()
mockSablier.filters.StreamCreated(),
);

const stream1 = await mockSablier.getStream(streamCreatedEvents[0].args.streamId);
Expand All @@ -508,7 +508,8 @@ describe('DecentHats_0_1_0', () => {
});

describe('Creating a new hat on existing Tree', () => {
let createHatAndAccountAndMintAndStreamsTx: ethers.ContractTransactionResponse;
let createRoleHatPromise: Promise<ethers.ContractTransactionResponse>;
const topHatId = 0;

beforeEach(async () => {
await executeSafeTransaction({
Expand All @@ -532,25 +533,16 @@ describe('DecentHats_0_1_0', () => {
wearer: ethers.ZeroAddress,
sablierParams: [],
},
hats: [
{
maxSupply: 1,
details: '',
imageURI: '',
isMutable: false,
wearer: ethers.ZeroAddress,
sablierParams: [],
},
],
hats: [],
},
]
],
),
signers: [dao],
});

const currentBlockTimestamp = (await hre.ethers.provider.getBlock('latest'))!.timestamp;

createHatAndAccountAndMintAndStreamsTx = await executeSafeTransaction({
createRoleHatPromise = executeSafeTransaction({
safe: gnosisSafe,
to: decentHatsAddress,
transactionData: DecentHats_0_1_0__factory.createInterface().encodeFunctionData(
Expand Down Expand Up @@ -586,24 +578,55 @@ describe('DecentHats_0_1_0', () => {
await erc6551Registry.getAddress(),
mockHatsAccountImplementationAddress,
'0x5d0e6ce4fd951366cc55da93f6e79d8b81483109d79676a04bcc2bed6a4b5072',
]
],
),
signers: [dao],
});
});

it('Reverts if the top hat is not transferred to the DecentHats module first', async () => {
await expect(createRoleHatPromise).to.be.reverted;
});

it('Emits an ExecutionSuccess event', async () => {
await expect(createHatAndAccountAndMintAndStreamsTx).to.emit(
gnosisSafe,
'ExecutionSuccess'
);
// First transfer the top hat to the Safe
await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsAddress);
await expect(await createRoleHatPromise).to.emit(gnosisSafe, 'ExecutionSuccess');
});

it('Emits an ExecutionFromModuleSuccess event', async () => {
await expect(createHatAndAccountAndMintAndStreamsTx)
// First transfer the top hat to the Safe
await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsAddress);
await expect(await createRoleHatPromise)
.to.emit(gnosisSafe, 'ExecutionFromModuleSuccess')
.withArgs(decentHatsAddress);
});

it('Transfers the top hat back to the Safe', async () => {
// First transfer the top hat to the Safe
await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsAddress);

const isModuleWearerOfTopHat = await mockHats.isWearerOfHat(decentHatsAddress, topHatId);
expect(isModuleWearerOfTopHat).to.equal(true);

await createRoleHatPromise;

const isSafeWearerOfTopHat = await mockHats.isWearerOfHat(gnosisSafeAddress, topHatId);
expect(isSafeWearerOfTopHat).to.equal(true);
});

it('Actually creates the new hat', async () => {
// First transfer the top hat to the Safe
await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsAddress);

const hatsCountBeforeCreate = await mockHats.count();
expect(hatsCountBeforeCreate).to.equal(2); // Top hat + admin hat

await createRoleHatPromise;

const newHatId = await mockHats.count();
expect(newHatId).to.equal(3); // + newly created hat
});
});
});
});

0 comments on commit 0f91f6c

Please sign in to comment.