Skip to content

Commit

Permalink
Merge pull request #20 from dodger213/experimental/v1.4.1-yul-test
Browse files Browse the repository at this point in the history
Safe yul compatibility with test suite
  • Loading branch information
dodger213 authored Aug 18, 2024
2 parents e90d07c + b868432 commit e7dad00
Show file tree
Hide file tree
Showing 17 changed files with 256 additions and 14 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@ typechain-types
# Certora Formal Verification related files
.certora_internal
.certora_recent_jobs.json
.zip-output-url.txt
.zip-output-url.txt

# Safe YUL code
contracts/SafeBytecode.sol
19 changes: 19 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
JQ ?= jq

SAFEYULROOT ?=
SAFEYULBASE := build/yul/Safe.json
SAFEYUL := $(SAFEYULROOT)/$(SAFEYULBASE)

contracts/SafeBytecode.sol: $(SAFEYUL)
@echo '// SPDX-License-Identifier: LGPL-3.0-only' >$@
@echo 'pragma solidity >=0.7.0 <0.9.0;' >>$@
@echo '' >>$@
@echo 'contract SafeBytecode {' >>$@
@echo ' bytes public constant DEPLOYED_BYTECODE =' >>$@
@echo ' hex$(shell $(JQ) '.evm.deployedBytecode.object' $<);' >>$@
@echo '}' >>$@

.PHONY: $(SAFEYUL)
$(SAFEYUL):
@test -n "$(SAFEYULROOT)" || ( echo 'SAFEYULROOT not specified'; exit 1 )
@$(MAKE) -C $(SAFEYULROOT) $(SAFEYULBASE)
15 changes: 14 additions & 1 deletion contracts/Safe.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {ISignatureValidator, ISignatureValidatorConstants} from "./interfaces/IS
import {SafeMath} from "./external/SafeMath.sol";
import {ISafe} from "./interfaces/ISafe.sol";

import "./SafeBytecode.sol";

/**
* @title Safe - A multisignature wallet with support for confirmations using signed messages based on EIP-712.
* @dev Most important concepts:
Expand Down Expand Up @@ -69,13 +71,24 @@ contract Safe is
mapping(address => mapping(bytes32 => uint256)) public override approvedHashes;

// This constructor ensures that this contract can only be used as a singleton for Proxy contracts
constructor() {
constructor(address byteCode) {
/**
* By setting the threshold it is not possible to call setup anymore,
* so we create a Safe with 0 owners and threshold 1.
* This is an unusable Safe, perfect for the singleton
*/
threshold = 1;

if (byteCode != address(0)) {
bytes memory code = SafeBytecode(byteCode).DEPLOYED_BYTECODE();

/* solhint-disable no-inline-assembly */
/// @solidity memory-safe-assembly
assembly {
return(add(code, 32), mload(code))
}
/* solhint-enable no-inline-assembly */
}
}

/**
Expand Down
1 change: 1 addition & 0 deletions contracts/SafeL2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ contract SafeL2 is Safe {
* @inheritdoc Safe
*/
function onBeforeExecTransaction(

address to,
uint256 value,
bytes calldata data,
Expand Down
81 changes: 81 additions & 0 deletions contracts/accessors/SafeFallbackAccessor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;

contract SafeFallbackAccessor {
address private constant _SENTINEL = address(1);

address private _singleton;
mapping(address => address) private _modules;
mapping(address => address) private _owners;
uint256 private _ownerCount;
uint256 private _threshold;
uint256 private _nonce;
bytes32 private _deprecatedDomainSeparator;
mapping(bytes32 => uint256) private _signedMessages;
mapping(address => mapping(bytes32 => uint256)) private _approvedHashes;

function signedMessages(bytes32 hash) external view returns (bool signed) {
return _signedMessages[hash] != 0;
}

function getModulesPaginated(address start, uint256 pageSize)
public
view
returns (address[] memory modules, address next)
{
require(start == _SENTINEL || _modules[start] != address(0), "GS105");
require(pageSize > 0, "GS106");
modules = new address[](pageSize);

uint256 moduleCount = 0;
next = _modules[start];
while (next != address(0) && next != _SENTINEL && moduleCount < pageSize) {
modules[moduleCount] = next;
next = _modules[next];
moduleCount++;
}

if (next != _SENTINEL) {
next = modules[moduleCount - 1];
}

/* solhint-disable no-inline-assembly */
/// @solidity memory-safe-assembly
assembly {
mstore(modules, moduleCount)
}
/* solhint-enable no-inline-assembly */
}

function getModules() external view returns (address[] memory modules) {
address next;
(modules, next) = getModulesPaginated(_SENTINEL, 10);
require(next == address(0) || next == _SENTINEL, "GS107");
return modules;
}

function getOwners() external view returns (address[] memory array) {
array = new address[](_ownerCount);

uint256 index = 0;
address currentOwner = _owners[_SENTINEL];
while (currentOwner != _SENTINEL) {
array[index] = currentOwner;
currentOwner = _owners[currentOwner];
index++;
}
}

function getStorageAt(uint256 offset, uint256 length) external view returns (bytes memory result) {
result = new bytes(length * 32);
for (uint256 index = 0; index < length; index++) {
/* solhint-disable no-inline-assembly */
/// @solidity memory-safe-assembly
assembly {
let word := sload(add(offset, index))
mstore(add(add(result, 0x20), mul(index, 0x20)), word)
}
/* solhint-enable no-inline-assembly */
}
}
}
5 changes: 5 additions & 0 deletions contracts/handler/CompatibilityFallbackHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ import {ISignatureValidator} from "../interfaces/ISignatureValidator.sol";
import {ISafe} from "../interfaces/ISafe.sol";
import {HandlerContext} from "./HandlerContext.sol";

import "./SafeFallbackHandler.sol";

/**
* @title Compatibility Fallback Handler - Provides compatibility between pre 1.3.0 and 1.3.0+ Safe Smart Account contracts.
* @author Richard Meissner - @rmeissner
*/

contract CompatibilityFallbackHandler is TokenCallbackHandler, ISignatureValidator, HandlerContext {

// keccak256("SafeMessage(bytes message)");
bytes32 private constant SAFE_MSG_TYPEHASH = 0x60b3cbf8b4a223d68d641b3b6ddf9a298e7f33710cf3d3a9d1146b5a6150fbca;

Expand Down Expand Up @@ -157,4 +161,5 @@ contract CompatibilityFallbackHandler is TokenCallbackHandler, ISignatureValidat
}
/* solhint-enable no-inline-assembly */
}

}
84 changes: 84 additions & 0 deletions contracts/handler/SafeFallbackHandler.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;

import {SafeFallbackAccessor} from "../accessors/SafeFallbackAccessor.sol";

contract SafeFallbackHandler {
SafeFallbackAccessor private immutable _ACCESSOR;

constructor() {
_ACCESSOR = new SafeFallbackAccessor();
}

function signedMessages(bytes32) external view returns (bool) {
_fallbackToAccessor();
}

function getChainId() external view returns (uint256 chainId) {
/* solhint-disable no-inline-assembly */
/// @solidity memory-safe-assembly
assembly {
chainId := chainid()
}
/* solhint-enable no-inline-assembly */
}

function getModulesPaginated(address, uint256) external view returns (address[] memory, address) {
_fallbackToAccessor();
}

function getModules() external view returns (address[] memory) {
_fallbackToAccessor();
}

function getOwners() external view returns (address[] memory) {
_fallbackToAccessor();
}

function getStorageAt(uint256, uint256) external view returns (bytes memory) {
_fallbackToAccessor();
}

function simulate(address target, bytes calldata data) public returns (bytes memory result) {
bytes memory simulationCallData = abi.encodeWithSelector(0xb4faba09, target, data);

/* solhint-disable no-inline-assembly */
/// @solidity memory-safe-assembly
assembly {
pop(call(gas(), caller(), 0, add(simulationCallData, 0x20), mload(simulationCallData), 0x00, 0x20))

let responseSize := sub(returndatasize(), 0x20)
result := mload(0x40)
mstore(0x40, add(result, responseSize))
returndatacopy(result, 0x20, responseSize)

if iszero(mload(0x00)) { revert(add(result, 0x20), mload(result)) }
}
/* solhint-enable no-inline-assembly */
}

function _simulateAccessor(bytes calldata data) internal view returns (bytes memory result) {
function(address, bytes calldata) internal returns (bytes memory) _simulate = simulate;
function(address, bytes calldata) internal view returns (bytes memory) _simulateView;

/* solhint-disable no-inline-assembly */
/// @solidity memory-safe-assembly
assembly {
_simulateView := _simulate
}
/* solhint-enable no-inline-assembly */

return _simulateView(address(_ACCESSOR), data);
}

function _fallbackToAccessor() internal view {
bytes memory result = _simulateAccessor(msg.data);

/* solhint-disable no-inline-assembly */
/// @solidity memory-safe-assembly
assembly {
return(add(result, 0x20), mload(result))
}
/* solhint-enable no-inline-assembly */
}
}
7 changes: 7 additions & 0 deletions src/deploy/deploy_handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ const deploy: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
deterministicDeployment: true,
});

await deploy("SafeFallbackHandler", {
from: deployer,
args: [],
log: true,
deterministicDeployment: true,
});

await deploy("CompatibilityFallbackHandler", {
from: deployer,
args: [],
Expand Down
9 changes: 8 additions & 1 deletion src/deploy/deploy_safe_singleton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,18 @@ const deploy: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { deployer } = await getNamedAccounts();
const { deploy } = deployments;

await deploy("Safe", {
const safeBytecode = await deploy("SafeBytecode", {
from: deployer,
args: [],
log: true,
deterministicDeployment: true,
})

await deploy("Safe", {
from: deployer,
args: [safeBytecode.address],
log: true,
deterministicDeployment: true,
});
};

Expand Down
3 changes: 2 additions & 1 deletion test/accessors/SimulateTxAccessor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ describe("SimulateTxAccessor", () => {
const simulation = accessor.interface.decodeFunctionResult("simulate", acccessibleData);
expect(safe.interface.decodeFunctionResult("getOwners", simulation.returnData)[0]).to.be.deep.eq([user1.address]);
expect(simulation.success).to.be.true;
expect(simulation.estimate).to.be.lte(10000n);
expect(simulation.estimate.toNumber()).to.be.lte(15000);

});

it("simulate delegatecall", async () => {
Expand Down
2 changes: 2 additions & 0 deletions test/core/Safe.Incoming.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,15 @@ describe("Safe", () => {
});

it("should throw for incoming eth with data", async () => {

const {
safe,
signers: [user1],
} = await setupTests();
const safeAddress = await safe.getAddress();

await expect(user1.sendTransaction({ to: safeAddress, value: 23, data: "0xbaddad" })).to.be.reverted;

});
});
});
Loading

0 comments on commit e7dad00

Please sign in to comment.