Skip to content

Commit 27c28db

Browse files
authored
Merge pull request #302 from OffchainLabs/custom-fee-rollup-ci
Custom fee rollup ci
2 parents d15f9d7 + fbafe1b commit 27c28db

File tree

13 files changed

+760
-3
lines changed

13 files changed

+760
-3
lines changed

hardhat.config.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@ const solidity = {
5353
evmVersion: 'cancun',
5454
},
5555
},
56+
'src/mocks/ArbOS11To32UpgradeTest.sol': {
57+
version: '0.8.24',
58+
settings: {
59+
optimizer: {
60+
enabled: true,
61+
runs: 100,
62+
},
63+
evmVersion: 'cancun',
64+
},
65+
},
5666
},
5767
}
5868

@@ -89,6 +99,16 @@ if (process.env['INTERFACE_TESTER_SOLC_VERSION']) {
8999
evmVersion: 'cancun',
90100
},
91101
},
102+
'src/mocks/ArbOS11To32UpgradeTest.sol': {
103+
version: '0.8.24',
104+
settings: {
105+
optimizer: {
106+
enabled: true,
107+
runs: 100,
108+
},
109+
evmVersion: 'cancun',
110+
},
111+
},
92112
}
93113
}
94114

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@arbitrum/nitro-contracts",
3-
"version": "3.0.0",
3+
"version": "3.0.1-beta.0",
44
"description": "Layer 2 precompiles and rollup for Arbitrum Nitro",
55
"author": "Offchain Labs, Inc.",
66
"license": "BUSL-1.1",
@@ -42,6 +42,7 @@
4242
"test:e2e:orbit": "hardhat test test/e2e/orbitChain.ts",
4343
"test:e2e:orbit-fee-token-rollup": "hardhat test test/e2e/customFeeRollup.ts",
4444
"test:e2e": "hardhat test test/e2e/*.ts",
45+
"test:e2e:stylus": "hardhat test test/e2e/stylusDeployer.ts",
4546
"test:upgrade": "./scripts/testUpgrade.bash",
4647
"test:foundry": "forge test --gas-limit 10000000000",
4748
"test:update": "yarn run test:signatures || yarn run test:storage",

scripts/printMetadataHashes.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,16 @@ async function main() {
1616
'Outbox',
1717
'SequencerInbox',
1818
'Bridge',
19+
'RollupEventInbox',
1920
'ERC20Inbox',
2021
'ERC20Outbox',
2122
'SequencerInbox',
2223
'ERC20Bridge',
24+
'ERC20RollupEventInbox',
2325
'RollupProxy',
2426
'RollupAdminLogic',
2527
'RollupUserLogic',
26-
'ChallengeManager',
28+
'EdgeChallengeManager',
2729
]
2830

2931
// Print the current git tag

slither.db.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2024, Offchain Labs, Inc.
2+
// For license information, see https://github.com/OffchainLabs/nitro-contracts/blob/main/LICENSE
3+
// SPDX-License-Identifier: BUSL-1.1
4+
5+
pragma solidity ^0.8.24;
6+
7+
import "../precompiles/ArbSys.sol";
8+
9+
contract ArbOS11To32UpgradeTest {
10+
function mcopy() external returns (bytes32 x) {
11+
assembly {
12+
mstore(0x20, 0x9) // Store 0x9 at word 1 in memory
13+
mcopy(0, 0x20, 0x20) // Copies 0x9 to word 0 in memory
14+
x := mload(0) // Returns 32 bytes "0x9"
15+
}
16+
require(ArbSys(address(0x64)).arbOSVersion() == 55 + 32, "EXPECTED_ARBOS_32");
17+
}
18+
}

src/mocks/BigMap.sol

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2025, Offchain Labs, Inc.
2+
// For license information, see:
3+
// https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md
4+
// SPDX-License-Identifier: BUSL-1.1
5+
6+
pragma solidity ^0.8.0;
7+
8+
contract BigMap {
9+
mapping(uint256 => uint256) public data;
10+
uint256 size;
11+
12+
function clearAndAddValues(uint256 clear, uint256 add) external {
13+
uint256 i = size;
14+
while (i < size + add) {
15+
data[i] = 8675309;
16+
i++;
17+
}
18+
size = i;
19+
for (uint256 j = 0; j < clear; j++) {
20+
data[j] = 0;
21+
}
22+
}
23+
}

src/stylus/StylusDeployer.sol

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.17;
3+
4+
import {ArbWasm} from "../precompiles/ArbWasm.sol";
5+
6+
/// @title A Stylus contract deployer, activator and initializer
7+
/// @author The name of the author
8+
/// @notice Stylus contracts do not support constructors. Instead, Stylus devs can use this contract to deploy and
9+
/// initialize their contracts atomically
10+
contract StylusDeployer {
11+
ArbWasm constant ARB_WASM = ArbWasm(0x0000000000000000000000000000000000000071);
12+
13+
event ContractDeployed(address deployedContract);
14+
15+
error ContractDeploymentError(bytes bytecode);
16+
error ContractInitializationError(address newContract);
17+
error RefundExcessValueError(uint256 excessValue);
18+
error EmptyBytecode();
19+
error InitValueButNotInitData();
20+
21+
/// @notice Deploy, activate and initialize a stylus contract
22+
/// In order to call a stylus contract, and therefore initialize it, it must first be activated.
23+
/// This contract provides an atomic way of deploying, activating and initializing a stylus contract.
24+
///
25+
/// Initialisation will be called if initData is supplied, other initialization is skipped
26+
/// Activation is not always necessary. If a contract has the same code has as another recently activated
27+
/// contract then activation will be skipped.
28+
/// If additional value remains in the contract after activation it will be transferred to the msg.sender
29+
/// to that end the caller must ensure that they can receive eth.
30+
///
31+
/// The caller should do the following before calling this contract:
32+
/// 1. Check whether the contract will require activation, and if so what the cost will be.
33+
/// This can be done by spoofing an address with the contract code, then calling ArbWasm.programVersion to compare the
34+
/// the returned result against ArbWasm.stylusVersion. If activation is required ArbWasm.activateProgram can then be called
35+
/// to find the returned dataFee.
36+
/// 2. Next this deploy function can be called. The value of the call must be set to the previously ascertained dataFee + initValue
37+
/// If activation is not require, the value of the call should be set to initValue
38+
///
39+
/// Note: Stylus contract caching is not done by the deployer, and will have to be done separately after deployment.
40+
/// See https://docs.arbitrum.io/stylus/how-tos/caching-contracts for more details on caching
41+
/// @param bytecode The bytecode of the stylus contract to be deployed
42+
/// @param initData Initialisation call data. After deployment the contract will be called with this data
43+
/// If no initialisation data is provided then the newly deployed contract will not be called.
44+
/// This means that the receive or dataless fallback function cannot be called from this contract.
45+
/// @param initValue Initialisation value. After deployment, the contract will be called with the init data and this value.
46+
/// At least as much eth as init value must be provided with this call. Init value is specified here separately
47+
/// rather than using the msg.value since the msg.value may need to be greater than the init value to accomodate activation data fee.
48+
/// See the @notice block above for more details.
49+
/// @param salt If a non zero salt is provided the contract will be created using CREATE2 instead of CREATE
50+
/// The supplied salt will be hashed with the initData so that wherever the address is observed
51+
/// it was initialised with the same variables.
52+
/// @return The address of the deployed conract
53+
function deploy(
54+
bytes calldata bytecode,
55+
bytes calldata initData,
56+
uint256 initValue,
57+
bytes32 salt
58+
) public payable returns (address) {
59+
if (salt != 0) {
60+
// if a salt was supplied, hash the salt with the init data. This guarantees that
61+
// anywhere the address of this contract is seen the same init data was used
62+
salt = initSalt(salt, initData);
63+
}
64+
65+
address newContractAddress = deployContract(bytecode, salt);
66+
bool shouldActivate = requiresActivation(newContractAddress);
67+
uint256 dataFee = 0;
68+
if (shouldActivate) {
69+
// ensure there will be enough left over for init
70+
// activateProgram will return unused value back to this contract without an EVM call
71+
uint256 activationValue = msg.value - initValue;
72+
(, dataFee) = ARB_WASM.activateProgram{value: activationValue}(newContractAddress);
73+
}
74+
75+
// initialize - this will fail if the program wasn't activated by this point
76+
// we check if initData exists to avoid calling contracts unnecessarily
77+
if (initData.length != 0) {
78+
(bool success,) = address(newContractAddress).call{value: initValue}(initData);
79+
if (!success) {
80+
revert ContractInitializationError(newContractAddress);
81+
}
82+
} else if (initValue != 0) {
83+
// if initValue exists init data should too
84+
revert InitValueButNotInitData();
85+
}
86+
87+
// refund any remaining value
88+
uint256 bal = msg.value - dataFee - initValue;
89+
if (bal != 0) {
90+
// the caller must be payable
91+
(bool sent,) = payable(msg.sender).call{value: bal}("");
92+
if (!sent) {
93+
revert RefundExcessValueError(bal);
94+
}
95+
}
96+
97+
// activation already emits the following event:
98+
// event ProgramActivated(bytes32 indexed codehash, bytes32 moduleHash, address program, uint256 dataFee, uint16 version);
99+
emit ContractDeployed(newContractAddress);
100+
101+
return newContractAddress;
102+
}
103+
104+
/// @notice When using CREATE2 the deployer includes the init data and value in the salt so that callers
105+
/// can be sure that wherever they encourter this address it was initialized with the same data and value
106+
/// @param salt A user supplied salt
107+
/// @param initData The init data that will be used to init the deployed contract
108+
function initSalt(bytes32 salt, bytes calldata initData) public pure returns (bytes32) {
109+
return keccak256(abi.encodePacked(salt, initData));
110+
}
111+
112+
/// @notice Checks whether a contract requires activation
113+
function requiresActivation(
114+
address addr
115+
) public view returns (bool) {
116+
// currently codeHashVersion returns an error when codeHashVersion != stylus version
117+
// so we do a try/catch to to check it
118+
uint16 codeHashVersion;
119+
try ARB_WASM.codehashVersion(addr.codehash) returns (uint16 version) {
120+
codeHashVersion = version;
121+
} catch {
122+
// stylus version is always >= 1
123+
codeHashVersion = 0;
124+
}
125+
126+
// due to the bug in ARB_WASM.codeHashVersion we know that codeHashVersion will either be 0 or the current
127+
// version. We still check that is not equal to the stylusVersion
128+
return codeHashVersion != ARB_WASM.stylusVersion();
129+
}
130+
131+
/// @notice Deploy the a contract with the supplied bytecode.
132+
/// Will create2 if the supplied salt is non zero
133+
function deployContract(bytes memory bytecode, bytes32 salt) internal returns (address) {
134+
if (bytecode.length == 0) {
135+
revert EmptyBytecode();
136+
}
137+
138+
address newContractAddress;
139+
if (salt != 0) {
140+
assembly {
141+
newContractAddress := create2(0, add(bytecode, 0x20), mload(bytecode), salt)
142+
}
143+
} else {
144+
assembly {
145+
newContractAddress := create(0, add(bytecode, 0x20), mload(bytecode))
146+
}
147+
}
148+
149+
// bubble up the revert if there was one
150+
assembly {
151+
if and(iszero(newContractAddress), not(iszero(returndatasize()))) {
152+
let p := mload(0x40)
153+
returndatacopy(p, 0, returndatasize())
154+
revert(p, returndatasize())
155+
}
156+
}
157+
158+
if (newContractAddress == address(0)) {
159+
revert ContractDeploymentError(bytecode);
160+
}
161+
162+
return newContractAddress;
163+
}
164+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
/// @notice A call forward that doesnt implement the receive or fallback functions, so cant receive value
5+
contract NoReceiveForwarder {
6+
function forward(address to, bytes calldata data) public payable {
7+
(bool success,) = address(to).call{value: msg.value}(data);
8+
require(success, "call forward failed");
9+
}
10+
}
11+
12+
/// @notice A call forward that does implement the receive or fallback functions, so cant receive value
13+
contract ReceivingForwarder {
14+
function forward(address to, bytes calldata data) public payable {
15+
(bool success,) = address(to).call{value: msg.value}(data);
16+
require(success, "call forward failed");
17+
}
18+
19+
receive() external payable {}
20+
}
21+
22+
/// @notice Errors upon construction
23+
contract ConstructorError {
24+
constructor() {
25+
require(false, "test error in constructor");
26+
}
27+
}
28+
29+
/// @notice Errors upon construction
30+
contract ConstructorFine {
31+
constructor() {
32+
require(true, "test error in constructor");
33+
}
34+
35+
function number() public pure returns (uint256) {
36+
return 0;
37+
}
38+
}

0 commit comments

Comments
 (0)