From 05eba0139d2bac5493c453f04b629e9beeb5be3a Mon Sep 17 00:00:00 2001 From: Mike-CZ Date: Mon, 27 Jan 2025 10:51:07 +0100 Subject: [PATCH] Refactor burn native tokens method (#123) --- contracts/sfc/SFC.sol | 24 ++++++++++++++++-------- test/SFC.ts | 30 +++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/contracts/sfc/SFC.sol b/contracts/sfc/SFC.sol index 159f91f..b81e43f 100644 --- a/contracts/sfc/SFC.sol +++ b/contracts/sfc/SFC.sol @@ -152,6 +152,7 @@ contract SFC is OwnableUpgradeable, UUPSUpgradeable, Version { // values error ZeroAmount(); error ZeroRewards(); + error ValueTooLarge(); // pubkeys error PubkeyUsedByOtherValidator(); @@ -216,7 +217,7 @@ contract SFC is OwnableUpgradeable, UUPSUpgradeable, Version { ); event ClaimedRewards(address indexed delegator, uint256 indexed toValidatorID, uint256 rewards); event RestakedRewards(address indexed delegator, uint256 indexed toValidatorID, uint256 rewards); - event BurntFTM(uint256 amount); + event BurntNativeTokens(uint256 amount); event UpdatedSlashingRefundRatio(uint256 indexed validatorID, uint256 refundRatio); event RefundedSlashedLegacyDelegation(address indexed delegator, uint256 indexed validatorID, uint256 amount); event AnnouncedRedirection(address indexed from, address indexed to); @@ -459,9 +460,12 @@ contract SFC is OwnableUpgradeable, UUPSUpgradeable, Version { emit TreasuryFeesResolved(fees); } - /// burnFTM allows SFC to burn an arbitrary amount of FTM tokens. - function burnFTM(uint256 amount) external onlyOwner { - _burnFTM(amount); + /// Burn native tokens by sending them to the SFC contract. + function burnNativeTokens() external payable { + if (msg.value == 0) { + revert ZeroAmount(); + } + _burnNativeTokens(msg.value); } /// Issue tokens to the issued tokens recipient as a counterparty to the burnt FTM tokens. @@ -753,7 +757,7 @@ contract SFC is OwnableUpgradeable, UUPSUpgradeable, Version { if (!sent) { revert TransferFailed(); } - _burnFTM(penalty); + _burnNativeTokens(penalty); emit Withdrawn(delegator, toValidatorID, wrID, amount - penalty, penalty); } @@ -817,12 +821,16 @@ contract SFC is OwnableUpgradeable, UUPSUpgradeable, Version { return rewards; } - /// Burn FTM tokens. + /// Burn native tokens. /// The tokens are sent to the zero address. - function _burnFTM(uint256 amount) internal { + function _burnNativeTokens(uint256 amount) internal { if (amount != 0) { + if (amount > totalSupply) { + revert ValueTooLarge(); + } + totalSupply -= amount; payable(address(0)).transfer(amount); - emit BurntFTM(amount); + emit BurntNativeTokens(amount); } } diff --git a/test/SFC.ts b/test/SFC.ts index b3ea8d6..0b7ea7e 100644 --- a/test/SFC.ts +++ b/test/SFC.ts @@ -8,6 +8,7 @@ import { BlockchainNode, ValidatorMetrics } from './helpers/BlockchainNode'; describe('SFC', () => { const fixture = async () => { const [owner, user] = await ethers.getSigners(); + const totalSupply = ethers.parseEther('100'); const sfc = await upgrades.deployProxy(await ethers.getContractFactory('UnitTestSFC'), { kind: 'uups', initializer: false, @@ -24,7 +25,7 @@ describe('SFC', () => { const evmWriter: IEVMWriter = await ethers.deployContract('StubEvmWriter'); const initializer: UnitTestNetworkInitializer = await ethers.deployContract('UnitTestNetworkInitializer'); - await initializer.initializeAll(0, 0, sfc, nodeDriverAuth, nodeDriver, evmWriter, owner); + await initializer.initializeAll(0, totalSupply, sfc, nodeDriverAuth, nodeDriver, evmWriter, owner); const constants: UnitTestConstantsManager = await ethers.getContractAt( 'UnitTestConstantsManager', await sfc.constsAddress(), @@ -39,6 +40,7 @@ describe('SFC', () => { nodeDriver, nodeDriverAuth, constants, + totalSupply, }; }; @@ -55,6 +57,32 @@ describe('SFC', () => { ).to.revertedWithCustomError(this.sfc, 'TransfersNotAllowed'); }); + describe('Burn native tokens', () => { + it('Should revert when no amount sent', async function () { + await expect(this.sfc.connect(this.user).burnNativeTokens()).to.be.revertedWithCustomError( + this.sfc, + 'ZeroAmount', + ); + }); + + it('Should revert when amount greater than total supply', async function () { + await expect( + this.sfc.connect(this.user).burnNativeTokens({ value: this.totalSupply + 1n }), + ).to.be.revertedWithCustomError(this.sfc, 'ValueTooLarge'); + }); + + it('Should succeed and burn native tokens', async function () { + const amount = ethers.parseEther('1.5'); + const totalSupply = await this.sfc.totalSupply(); + const tx = await this.sfc.connect(this.user).burnNativeTokens({ value: amount }); + await expect(tx).to.emit(this.sfc, 'BurntNativeTokens').withArgs(amount); + expect(await this.sfc.totalSupply()).to.equal(totalSupply - amount); + await expect(tx).to.changeEtherBalance(this.sfc, 0); + await expect(tx).to.changeEtherBalance(this.user, -amount); + await expect(tx).to.changeEtherBalance(ethers.ZeroAddress, amount); + }); + }); + describe('Genesis validator', () => { beforeEach(async function () { const validator = ethers.Wallet.createRandom();