Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions contracts/sfc/SFC.sol
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ contract SFC is OwnableUpgradeable, UUPSUpgradeable, Version {
// values
error ZeroAmount();
error ZeroRewards();
error ValueTooLarge();

// pubkeys
error PubkeyUsedByOtherValidator();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could also use call instead of transfer

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is pending in #111

}
}

Expand Down
30 changes: 29 additions & 1 deletion test/SFC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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(),
Expand All @@ -39,6 +40,7 @@ describe('SFC', () => {
nodeDriver,
nodeDriverAuth,
constants,
totalSupply,
};
};

Expand All @@ -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();
Expand Down