Skip to content

Commit 098d6d5

Browse files
committed
contracts: make GovReward burn gas as required
1 parent 54bad8f commit 098d6d5

File tree

5 files changed

+115
-0
lines changed

5 files changed

+115
-0
lines changed

contracts/solidity/GovReward.sol

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
pragma solidity ^0.8.25;
33

44
import {Errors} from "./libraries/Errors.sol";
5+
import {Bytes} from "./libraries/Bytes.sol";
56
import {IGovReward} from "./interfaces/IGovReward.sol";
67
import {IGovernance} from "./interfaces/IGovernance.sol";
78
import {ERC1967Utils, GovProxyUpgradeable} from "./base/GovProxyUpgradeable.sol";
@@ -17,6 +18,17 @@ contract GovReward is IGovReward, GovProxyUpgradeable {
1718
if (msg.sig != bytes4(0xffffffff)) {
1819
revert Errors.InvalidSelector();
1920
}
21+
// burn required gas amount internally
22+
uint32 requiredSpace = Bytes.decodeUint32(msg.data[8:12]);
23+
if (requiredSpace < 21000) {
24+
revert Errors.InvalidGasLimit();
25+
}
26+
(bool success, bytes memory data) = address(this).staticcall{
27+
gas: requiredSpace - 21000
28+
}(abi.encodeWithSelector(this._wasteGas.selector));
29+
if (success || data.length > 0) {
30+
revert Errors.UnexpectedGasBurn();
31+
}
2032
}
2133

2234
modifier onlyGov() {
@@ -58,4 +70,8 @@ contract GovReward is IGovReward, GovProxyUpgradeable {
5870
(bool success, ) = to.call{value: value}(new bytes(0));
5971
if (!success) revert Errors.TransferFailed();
6072
}
73+
74+
function _wasteGas() external pure {
75+
while (true) {}
76+
}
6177
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.25;
3+
4+
library Bytes {
5+
function toBytes4(
6+
bytes memory data
7+
) internal pure returns (bytes4) {
8+
bytes4 out;
9+
for (uint256 i = 0; i < 4; i++) {
10+
out |= bytes4(data[i] & 0xFF) >> (i * 8);
11+
}
12+
return out;
13+
}
14+
15+
function decodeUint32(bytes memory data) internal pure returns (uint32) {
16+
require(data.length == 4, "Bad data");
17+
return uint32(toBytes4(data));
18+
}
19+
}

contracts/solidity/libraries/Errors.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ library Errors {
1313

1414
// GovReward Errors
1515
error InvalidSelector();
16+
error InvalidGasLimit();
17+
error UnexpectedGasBurn();
1618

1719
// Policy Errors
1820
error BlacklistExists();
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
contract MockFallback {
5+
function call_fallback(address addr, bytes calldata data) public payable {
6+
bool success;
7+
assembly {
8+
calldatacopy(0, data.offset, calldatasize())
9+
success := call(
10+
gas(),
11+
addr,
12+
0,
13+
0,
14+
calldatasize(),
15+
0,
16+
returndatasize()
17+
)
18+
switch success
19+
case 0 {
20+
invalid()
21+
}
22+
}
23+
}
24+
}

contracts/test/GovReward.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { ethers } from "hardhat";
2+
import { expect } from "chai";
3+
4+
const REWARD_PROXY = "0x1212000000000000000000000000000000000003";
5+
const REWARD_IMP = "0x1212100000000000000000000000000000000003";
6+
7+
describe("GovReward", function () {
8+
let Reward: any;
9+
let signers: any;
10+
11+
beforeEach(async function () {
12+
// Signers
13+
signers = await ethers.getSigners();
14+
15+
// Reset blockchain state
16+
await ethers.provider.send("hardhat_reset")
17+
18+
// Deploy contract
19+
const reward_deploy = await ethers.deployContract("GovReward");
20+
21+
// Copy Bytecode to native address
22+
const reward_code = await ethers.provider.send("eth_getCode", [reward_deploy.target]);
23+
await ethers.provider.send("hardhat_setCode", [REWARD_PROXY, reward_code]);
24+
25+
const contract = require("../artifacts/solidity/GovReward.sol/GovReward.json");
26+
Reward = new ethers.Contract(REWARD_PROXY, contract.abi, signers[0]);
27+
});
28+
29+
describe("envelope call", function () {
30+
let Mock: any;
31+
32+
beforeEach(async function () {
33+
Mock = await ethers.deployContract("MockFallback");
34+
});
35+
36+
it("Should revert if the selector is not 0xffffffff", async function () {
37+
await expect(
38+
Mock.call_fallback(REWARD_PROXY, "0xfffffffe")
39+
).to.be.reverted;
40+
});
41+
42+
it("Should revert if the declared gaslimit is lower than 21000", async function () {
43+
await expect(
44+
Mock.call_fallback(REWARD_PROXY, "0xffffffff")
45+
).to.be.reverted;
46+
});
47+
48+
it("Should consume gas as expected", async function () {
49+
await expect(
50+
Mock.call_fallback(REWARD_PROXY, "0xffffffff0000000000005208")
51+
).not.to.be.reverted;
52+
});
53+
});
54+
});

0 commit comments

Comments
 (0)