Skip to content

Commit

Permalink
refactor: add hook to delegate
Browse files Browse the repository at this point in the history
  • Loading branch information
skimaharvey committed Nov 25, 2024
1 parent 8e64b80 commit 9f02242
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 18 deletions.
20 changes: 8 additions & 12 deletions packages/lsp7-contracts/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,28 @@

## [0.15.0-rc.0](https://github.com/lukso-network/lsp-smart-contracts/compare/lsp7-contracts-v0.15.0-rc.0...lsp7-contracts-v0.15.0-rc.0) (2024-03-07)


### Miscellaneous Chores

* release 0.12.0 ([fbbec61](https://github.com/lukso-network/lsp-smart-contracts/commit/fbbec6199c6351721acedb35110fc1cc7bbb65ad))
* release 0.13.0 ([#817](https://github.com/lukso-network/lsp-smart-contracts/issues/817)) ([1bd2f5f](https://github.com/lukso-network/lsp-smart-contracts/commit/1bd2f5f699ecdbef857527cdac50df50dc051002))
- release 0.12.0 ([fbbec61](https://github.com/lukso-network/lsp-smart-contracts/commit/fbbec6199c6351721acedb35110fc1cc7bbb65ad))
- release 0.13.0 ([#817](https://github.com/lukso-network/lsp-smart-contracts/issues/817)) ([1bd2f5f](https://github.com/lukso-network/lsp-smart-contracts/commit/1bd2f5f699ecdbef857527cdac50df50dc051002))

## [0.15.0-rc.0](https://github.com/lukso-network/lsp-smart-contracts/compare/lsp7-contracts-v0.15.0-rc.0...lsp7-contracts-v0.15.0-rc.0) (2024-03-06)


### Miscellaneous Chores

* release 0.12.0 ([fbbec61](https://github.com/lukso-network/lsp-smart-contracts/commit/fbbec6199c6351721acedb35110fc1cc7bbb65ad))
* release 0.13.0 ([#817](https://github.com/lukso-network/lsp-smart-contracts/issues/817)) ([1bd2f5f](https://github.com/lukso-network/lsp-smart-contracts/commit/1bd2f5f699ecdbef857527cdac50df50dc051002))
- release 0.12.0 ([fbbec61](https://github.com/lukso-network/lsp-smart-contracts/commit/fbbec6199c6351721acedb35110fc1cc7bbb65ad))
- release 0.13.0 ([#817](https://github.com/lukso-network/lsp-smart-contracts/issues/817)) ([1bd2f5f](https://github.com/lukso-network/lsp-smart-contracts/commit/1bd2f5f699ecdbef857527cdac50df50dc051002))

## [0.15.0-rc.0](https://github.com/lukso-network/lsp-smart-contracts/compare/lsp7-contracts-v0.15.0-rc.0...lsp7-contracts-v0.15.0-rc.0) (2024-03-06)


### Miscellaneous Chores

* release 0.12.0 ([fbbec61](https://github.com/lukso-network/lsp-smart-contracts/commit/fbbec6199c6351721acedb35110fc1cc7bbb65ad))
* release 0.13.0 ([#817](https://github.com/lukso-network/lsp-smart-contracts/issues/817)) ([1bd2f5f](https://github.com/lukso-network/lsp-smart-contracts/commit/1bd2f5f699ecdbef857527cdac50df50dc051002))
- release 0.12.0 ([fbbec61](https://github.com/lukso-network/lsp-smart-contracts/commit/fbbec6199c6351721acedb35110fc1cc7bbb65ad))
- release 0.13.0 ([#817](https://github.com/lukso-network/lsp-smart-contracts/issues/817)) ([1bd2f5f](https://github.com/lukso-network/lsp-smart-contracts/commit/1bd2f5f699ecdbef857527cdac50df50dc051002))

## 0.15.0-rc.0 (2024-03-06)


### Miscellaneous Chores

* release 0.12.0 ([fbbec61](https://github.com/lukso-network/lsp-smart-contracts/commit/fbbec6199c6351721acedb35110fc1cc7bbb65ad))
* release 0.13.0 ([#817](https://github.com/lukso-network/lsp-smart-contracts/issues/817)) ([1bd2f5f](https://github.com/lukso-network/lsp-smart-contracts/commit/1bd2f5f699ecdbef857527cdac50df50dc051002))
- release 0.12.0 ([fbbec61](https://github.com/lukso-network/lsp-smart-contracts/commit/fbbec6199c6351721acedb35110fc1cc7bbb65ad))
- release 0.13.0 ([#817](https://github.com/lukso-network/lsp-smart-contracts/issues/817)) ([1bd2f5f](https://github.com/lukso-network/lsp-smart-contracts/commit/1bd2f5f699ecdbef857527cdac50df50dc051002))
11 changes: 11 additions & 0 deletions packages/lsp7-contracts/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ export const INTERFACE_ID_LSP7_PREVIOUS = {
};

export const LSP7_TYPE_IDS = {


// keccak256('LSP7Tokens_DelegatorNotification')
LSP7Tokens_DelegatorNotification:
'0x997bd66a7e7823b09383ec7ce65fc306af29b8f82a45627f8efc0408475de016',

// keccak256('LSP7Tokens_DelegateeNotification')
LSP7Tokens_DelegateeNotification:
'0x03fae98a28026f93c23e2c9438c2ef0faa101585127a89919d18f067d907b319',

// keccak256('LSP7Tokens_SenderNotification')
LSP7Tokens_SenderNotification:
'0x429ac7a06903dbc9c13dfcb3c9d11df8194581fa047c96d7a4171fc7402958ea',
Expand All @@ -17,4 +27,5 @@ export const LSP7_TYPE_IDS = {
// keccak256('LSP7Tokens_OperatorNotification')
LSP7Tokens_OperatorNotification:
'0x386072cc5a58e61263b434c722725f21031cd06e7c552cfaa06db5de8a320dbc',

};
6 changes: 6 additions & 0 deletions packages/lsp7-contracts/contracts/LSP7Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ bytes4 constant _INTERFACEID_LSP7 = 0xc52d6008;

// --- Token Hooks

// keccak256('LSP7Tokens_DelegatorNotification')
bytes32 constant _TYPEID_LSP7_DELEGATOR = 0x997bd66a7e7823b09383ec7ce65fc306af29b8f82a45627f8efc0408475de016;

// keccak256('LSP7Tokens_DelegateeNotification')
bytes32 constant _TYPEID_LSP7_DELEGATEE = 0x03fae98a28026f93c23e2c9438c2ef0faa101585127a89919d18f067d907b319;

// keccak256('LSP7Tokens_SenderNotification')
bytes32 constant _TYPEID_LSP7_TOKENSSENDER = 0x429ac7a06903dbc9c13dfcb3c9d11df8194581fa047c96d7a4171fc7402958ea;

Expand Down
33 changes: 33 additions & 0 deletions packages/lsp7-contracts/contracts/extensions/LSP7Votes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
pragma solidity ^0.8.0;

import {LSP7DigitalAsset} from "../LSP7DigitalAsset.sol";
import { LSP1Utils } from "@lukso/lsp1-contracts/contracts/LSP1Utils.sol";
import { _TYPEID_LSP7_DELEGATOR, _TYPEID_LSP7_DELEGATEE } from "../LSP7Constants.sol";
import {IERC5805} from "@openzeppelin/contracts/interfaces/IERC5805.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {Counters} from "@openzeppelin/contracts/utils/Counters.sol";



/**
* @dev Extension of LSP7 to support Compound-like voting and delegation. This version is more generic than Compound's,
* and supports token supply up to 2^224^ - 1, while COMP is limited to 2^96^ - 1.
Expand Down Expand Up @@ -272,6 +276,35 @@ abstract contract LSP7Votes is LSP7DigitalAsset, EIP712, IERC5805 {
emit DelegateChanged(delegator, currentDelegate, delegatee);

_moveVotingPower(currentDelegate, delegatee, delegatorBalance);

// Notify the delegator if it's not address(0)
if (delegator != address(0)) {
bytes memory delegatorNotificationData = abi.encode(
msg.sender,
delegatee,
delegatorBalance
);
LSP1Utils.notifyUniversalReceiver(
delegator,
_TYPEID_LSP7_DELEGATOR,
delegatorNotificationData
);
}

// Only notify the new delegatee if it's not address(0) and if there's actual voting power
if (delegatee != address(0) && delegatorBalance > 0) {
bytes memory delegateeNotificationData = abi.encode(
msg.sender,
delegator,
delegatorBalance
);

LSP1Utils.notifyUniversalReceiver(
delegatee,
_TYPEID_LSP7_DELEGATEE,
delegateeNotificationData
);
}
}

function _moveVotingPower(
Expand Down
72 changes: 66 additions & 6 deletions packages/lsp7-contracts/tests/LSP7Votes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { ethers } from 'hardhat';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { MyVotingToken, MyVotingToken__factory, MyGovernor, MyGovernor__factory } from '../types';
import { time, mine } from '@nomicfoundation/hardhat-network-helpers';
import { bigint } from 'hardhat/internal/core/params/argumentTypes';
import {
LSP7_TYPE_IDS
} from '../constants';


describe('Comprehensive Governor and Token Tests', () => {
let token: MyVotingToken;
Expand Down Expand Up @@ -120,13 +123,73 @@ describe('Comprehensive Governor and Token Tests', () => {
.transfer(voter2.address, voter1.address, ethers.parseEther('5'), true, '0x');
expect(await token.getVotes(voter1.address)).to.equal(ethers.parseEther('15'));
});

describe('Delegation Notifications', () => {
let mockUniversalReceiver;

beforeEach(async () => {
const MockUniversalReceiver = await ethers.getContractFactory('MockUniversalReceiver');
mockUniversalReceiver = await MockUniversalReceiver.deploy();
});

it('should notify delegator with correct data format', async () => {
await voter1.setUniversalReceiver(mockUniversalReceiver.address);

const expectedData = ethers.AbiCoder.defaultAbiCoder().encode(
['address', 'address', 'uint256'],
[voter1.address, voter2.address, ethers.parseEther('10')]
);

await expect(token.connect(voter1).delegate(voter2.address))
.to.emit(mockUniversalReceiver, 'UniversalReceiverCalled')
.withArgs(
token.target,
LSP7_TYPE_IDS.LSP7Tokens_DelegatorNotification,
expectedData
);
});

it('should notify delegatee with correct data format', async () => {
await voter2.setUniversalReceiver(mockUniversalReceiver.address);

const expectedData = ethers.AbiCoder.defaultAbiCoder().encode(
['address', 'address', 'uint256'],
[voter1.address, voter1.address, ethers.parseEther('10')]
);

await expect(token.connect(voter1).delegate(voter2.address))
.to.emit(mockUniversalReceiver, 'UniversalReceiverCalled')
.withArgs(
token.target,
LSP7_TYPE_IDS.LSP7Tokens_DelegateeNotification,
expectedData
);
});

it('should not notify delegatee when delegator has zero balance', async () => {
await voter2.setUniversalReceiver(mockUniversalReceiver.address);

const [zeroBalanceAccount] = await ethers.getSigners();

await expect(token.connect(zeroBalanceAccount).delegate(voter2.address)).to.not.emit(
mockUniversalReceiver,
'UniversalReceiverCalled',
);
});

it('should not notify address(0)', async () => {
await expect(token.connect(voter1).delegate(ethers.ZeroAddress)).to.not.emit(
mockUniversalReceiver,
'UniversalReceiverCalled',
);
});
});
});

describe('Voting Process and Proposal Lifecycle', () => {
let proposalId;
let proposalId: string;

beforeEach(async () => {
// Setup for voting tests
await token.connect(proposer).delegate(proposer.address);
await token.connect(voter1).delegate(voter1.address);
await token.connect(voter2).delegate(voter2.address);
Expand Down Expand Up @@ -185,15 +248,12 @@ describe('Comprehensive Governor and Token Tests', () => {
await mine(VOTING_DELAY + 1);
await governor.connect(voter3).castVote(proposalId, 1); // Ensure quorum and pass

// Try to execute immediately after voting period
await expect(
governor.execute([randomEOA.address], [0], ['0xaabbccdd'], ethers.id('Proposal #1')),
).to.be.revertedWith('Governor: proposal not successful');

// Move past timelock period
await mine(await governor.votingPeriod());

// Now execution should succeed
await expect(
governor.execute([randomEOA.address], [0], ['0xaabbccdd'], ethers.id('Proposal #1')),
).to.emit(governor, 'ProposalExecuted');
Expand Down

0 comments on commit 9f02242

Please sign in to comment.