Skip to content

Commit bcecd07

Browse files
authored
Merge pull request #205 from oasisprotocol/CedarMist/gaspad
contracts: add gaspad and gasused precompiles
2 parents fcd5102 + 5d11ac8 commit bcecd07

File tree

3 files changed

+88
-0
lines changed

3 files changed

+88
-0
lines changed

contracts/contracts/Sapphire.sol

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ library Sapphire {
2323
0x0100000000000000000000000000000000000007;
2424
address internal constant CURVE25519_PUBLIC_KEY =
2525
0x0100000000000000000000000000000000000008;
26+
address internal constant GAS_USED =
27+
0x0100000000000000000000000000000000000009;
28+
address internal constant PAD_GAS =
29+
0x010000000000000000000000000000000000000a;
2630

2731
// Oasis-specific, general precompiles
2832
address internal constant SHA512_256 =
@@ -225,6 +229,28 @@ library Sapphire {
225229
require(success, "verify: failed");
226230
return abi.decode(v, (bool));
227231
}
232+
233+
/**
234+
* @dev Set the current transactions gas usage to a specific amount
235+
* @param toAmount Gas usage will be set to this amount
236+
* @custom:see @oasisprotocol/oasis-sdk :: precompile/gas.rs :: call_pad_gas
237+
*
238+
* Will cause a reversion if the current usage is more than the amount
239+
*/
240+
function padGas(uint128 toAmount) internal view {
241+
(bool success, ) = PAD_GAS.staticcall(abi.encode(toAmount));
242+
require(success, "verify: failed");
243+
}
244+
245+
/**
246+
* @dev Returns the amount of gas currently used by the transaction
247+
* @custom:see @oasisprotocol/oasis-sdk :: precompile/gas.rs :: call_gas_used
248+
*/
249+
function gasUsed() internal view returns (uint64) {
250+
(bool success, bytes memory v) = GAS_USED.staticcall("");
251+
require(success, "gasused: failed");
252+
return abi.decode(v, (uint64));
253+
}
228254
}
229255

230256
/**

contracts/contracts/tests/Gas.sol

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
pragma solidity ^0.8.0;
4+
5+
import {Sapphire} from "../Sapphire.sol";
6+
7+
contract GasTests {
8+
bytes32 tmp;
9+
10+
function testConstantTime(uint256 useGas, uint128 padGasAmount) external {
11+
if (useGas == 1) {
12+
bytes32 x;
13+
14+
for (uint256 i = 0; i < 100; i++) {
15+
x = keccak256(abi.encodePacked(x, tmp));
16+
}
17+
18+
tmp = x;
19+
}
20+
21+
Sapphire.padGas(padGasAmount);
22+
}
23+
}

contracts/test/gas.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
import { expect } from 'chai';
4+
import { ethers } from 'hardhat';
5+
import { GasTests__factory } from '../typechain-types/factories/contracts/tests/Gas.sol';
6+
import { GasTests } from '../typechain-types/contracts/tests/Gas.sol/GasTests';
7+
8+
describe('Gas Padding', function () {
9+
let contract: GasTests;
10+
11+
before(async () => {
12+
const factory = (await ethers.getContractFactory(
13+
'GasTests',
14+
)) as GasTests__factory;
15+
contract = await factory.deploy();
16+
});
17+
18+
it('Gas Padding works as Expected', async () => {
19+
const expectedGas = 122735;
20+
21+
let tx = await contract.testConstantTime(1, 100000);
22+
let receipt = await tx.wait();
23+
expect(receipt.cumulativeGasUsed).eq(expectedGas);
24+
25+
tx = await contract.testConstantTime(2, 100000);
26+
receipt = await tx.wait();
27+
expect(receipt.cumulativeGasUsed).eq(expectedGas);
28+
29+
tx = await contract.testConstantTime(1, 100000);
30+
receipt = await tx.wait();
31+
expect(receipt.cumulativeGasUsed).eq(expectedGas);
32+
33+
// Note: calldata isn't included in gas padding
34+
// Thus when the value is 0 it will use 4 gas instead of 16 gas
35+
tx = await contract.testConstantTime(0, 100000);
36+
receipt = await tx.wait();
37+
expect(receipt.cumulativeGasUsed).eq(expectedGas - 12);
38+
});
39+
});

0 commit comments

Comments
 (0)