Skip to content

Commit

Permalink
add docs
Browse files Browse the repository at this point in the history
  • Loading branch information
gnkz committed Oct 31, 2024
1 parent 1e46f50 commit 9a09c5f
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 72 deletions.
7 changes: 7 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Copyright 2024 Gonzalo Sánchez

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
68 changes: 9 additions & 59 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,66 +1,16 @@
## Foundry
# Read-Only Solidity Proxy

**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**
> [!CAUTION]
> This is a just-for-fun idea I had and the contract is not audited so use with caution.
Foundry consists of:
`ReadOnlyProxy` is a smart contract that enables call delegation while preserving the state context, ensuring that any calls made through the proxy are restricted to read-only access to the storage.

- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
- **Chisel**: Fast, utilitarian, and verbose solidity REPL.
This read-only access is enforced by using `staticcall` to call a function that performs a `delegatecall` to the target contract.

## Documentation
## Build

https://book.getfoundry.sh/
`forge build`

## Usage
## Tests

### Build

```shell
$ forge build
```

### Test

```shell
$ forge test
```

### Format

```shell
$ forge fmt
```

### Gas Snapshots

```shell
$ forge snapshot
```

### Anvil

```shell
$ anvil
```

### Deploy

```shell
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
```

### Cast

```shell
$ cast <subcommand>
```

### Help

```shell
$ forge --help
$ anvil --help
$ cast --help
```
`forge test`
44 changes: 31 additions & 13 deletions src/ReadOnlyProxy.sol
Original file line number Diff line number Diff line change
@@ -1,36 +1,57 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @title A read-only proxy contract
/// @author Gonzalo Sánchez <[email protected]>
/// @notice The purpose of this contract is to have a way to proxy calls to another contract but
/// making sure those calls are not able to modify the state. This could be useful in scenarios
/// where there are needs to decrease smart contract sizes by removing public read only functions
/// @dev To make sure the call is read only there is a `delegatecall` that's invoked inside a
/// `staticcall`, this way the delegated contract has access to the storage but is limited by the
/// `staticcall` so if there is any modification to the state the call will revert
abstract contract ReadOnlyProxy {
error DelegateCallNotAllowed();

function _delegate(bytes memory _data) public {
receive() external payable {
_beforeFallback();
_fallback();
}

fallback() external payable {
_beforeFallback();
_fallback();
}

/// @notice function in charge of doing a `delegatecall` to the `reader` contract
/// @param _data the abi encoded method to call on `reader`
function _delegate(bytes memory _data) external {
// can only be called from self
if (msg.sender != address(this)) {
revert DelegateCallNotAllowed();
}

address reader = _reader();

assembly {
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
// delegate the call to the `reader`
let result := delegatecall(gas(), reader, add(_data, 0x20), mload(_data), 0, 0)

// Copy the returned data.
returndatacopy(0, 0, returndatasize())

switch result
// delegatecall returns 0 on error.
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}

/// @notice called inside the `fallback` or `receive` functions. It does an `staticcall` on
/// `_delegate` proxying `msg.data`
function _fallback() internal view {
address self = address(this);
bytes memory data = abi.encodeWithSignature("_delegate(bytes)", msg.data);
bytes memory data = abi.encodeWithSelector(ReadOnlyProxy._delegate.selector, msg.data);

assembly {
// do an static call to `self._delegate` using `msg.data`
let result := staticcall(gas(), self, add(data, 0x20), mload(data), 0, 0)

returndatacopy(0, 0, returndatasize())
Expand All @@ -41,13 +62,10 @@ abstract contract ReadOnlyProxy {
}
}

/// @notice function used to get the `reader` contract address
function _reader() internal view virtual returns (address);

fallback() external payable {
_fallback();
}

receive() external payable {
_fallback();
}
/// @notice called before the `_fallback` function
/// @dev just for flexibility
function _beforeFallback() internal virtual {}
}

0 comments on commit 9a09c5f

Please sign in to comment.