Commit 3df3cb86 authored by vicotor's avatar vicotor

add contract deploy

parent f7285651

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

# Admin account
export GS_ADMIN_ADDRESS=
export GS_ADMIN_PRIVATE_KEY=
# Batcher account
export GS_BATCHER_ADDRESS=
export GS_BATCHER_PRIVATE_KEY=
# Proposer account
export GS_PROPOSER_ADDRESS=
export GS_PROPOSER_PRIVATE_KEY=
# Sequencer account
export GS_SEQUENCER_ADDRESS=
export GS_SEQUENCER_PRIVATE_KEY=
##################################################
# Chain Information #
##################################################
# L1 chain information
export L1_CHAIN_ID=10099
export L1_BLOCK_TIME=3
# L2 chain information
export L2_CHAIN_ID=42069
export L2_BLOCK_TIME=1
##################################################
# op-node Configuration #
##################################################
# The kind of RPC provider, used to inform optimal transactions receipts
# fetching. Valid options: alchemy, quicknode, infura, parity, nethermind,
# debug_geth, erigon, basic, any.
export L1_RPC_KIND=basic
##################################################
# Contract Deployment #
##################################################
# RPC URL for the L1 network to interact with
export L1_RPC_URL=http://172.80.1.2:8545
# Salt used via CREATE2 to determine implementation addresses
# NOTE: If you want to deploy contracts from scratch you MUST reload this
# variable to ensure the salt is regenerated and the contracts are
# deployed to new addresses (otherwise deployment will fail)
export IMPL_SALT=$(openssl rand -hex 32)
# Name for the deployed network
export DEPLOYMENT_CONTEXT=exchain
# Optional Tenderly details for simulation link during deployment
export TENDERLY_PROJECT=
export TENDERLY_USERNAME=
# Optional Etherscan API key for contract verification
export ETHERSCAN_API_KEY=
# Private key to use for contract deployments, you don't need to worry about
# this for the Getting Started guide.
export PRIVATE_KEY=
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
init: init:
docker run --rm --name initlayer1 -v "${PWD}/layer1:/root" tscel/eth:v1.10.26 geth init --datadir /root/node/data /root/genesis.json docker run --rm --name initlayer1 -v "${PWD}/layer1:/root" tscel/eth:v1.10.26 geth init --datadir /root/node/data /root/genesis.json
build:
docker build --no-cache -t tscel/exchain:contracts -f ./build/Dockerfile.contracts .
start: start:
docker compose up -d docker compose up -d
......
FROM ghcr.io/foundry-rs/foundry
WORKDIR /app
COPY ./layer2/contracts-bedrock ./contracts
\ No newline at end of file
version: '3' version: '3'
networks: networks:
exchain: exchain:
driver: bridge ipam:
driver: default
config:
- subnet: 172.80.0.0/16
services: services:
layer1: layer1:
image: tscel/eth:v1.10.26 image: tscel/eth:v1.10.26
container_name: layer1
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "3"
networks: networks:
- exchain exchain:
ipv4_address: 172.80.1.2
ports: ports:
- "8545:8545" - "8545:8545"
entrypoint: geth --datadir /root/node/data --http --nodiscover --miner.gaslimit=10000000000 --http.api=eth,net,web3,admin,txpool,miner,debug --http.addr=0.0.0.0 --http.corsdomain=* --authrpc.vhosts=* --authrpc.addr=0.0.0.0 --authrpc.port=8552 --rpc.allow-unprotected-txs --authrpc.jwtsecret=/root/node/data/jwtsecret --allow-insecure-unlock --unlock=0xfEe2882b7d75FadDcebD002E7e3bEf7B19Eed14E --password=/root/password.txt --mine --gcmode archive entrypoint: geth --datadir /root/node/data --http --nodiscover --miner.gaslimit=10000000000 --http.api=eth,net,web3,admin,txpool,miner,debug --http.addr=0.0.0.0 --http.corsdomain=* --authrpc.vhosts=* --authrpc.addr=0.0.0.0 --authrpc.port=8552 --rpc.allow-unprotected-txs --authrpc.jwtsecret=/root/node/data/jwtsecret --allow-insecure-unlock --unlock=0xfEe2882b7d75FadDcebD002E7e3bEf7B19Eed14E --password=/root/password.txt --mine --gcmode archive
volumes: volumes:
- "./layer1/node:/root/node" - "./layer1/node:/root/node"
- "./layer1/password.txt:/root/password.txt" - "./layer1/password.txt:/root/password.txt"
environment:
- MINER=0xfEe2882b7d75FadDcebD002E7e3bEf7B19Eed14E
\ No newline at end of file forge-init:
image: ghcr.io/foundry-rs/foundry:latest
networks:
- exchain
entrypoint: /bin/sh -c "sleep 5 && cast publish --rpc-url http://172.80.1.2:8545 0xf8a58085174876e800830186a08080b853604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf31ba02222222222222222222222222222222222222222222222222222222222222222a02222222222222222222222222222222222222222222222222222222222222222"
depends_on:
- layer1
forge-deploy:
image: tscel/exchain:contracts
networks:
- exchain
volumes:
- "./layer2/deployments:/app/deployments"
- "./layer2/contracts-bedrock:/app/contracts"
- ".envrc:/app/.envrc"
# entrypoint: bash -c "echo 12233"
entrypoint: /app/contracts/deploy.sh
depends_on:
- forge-init
forge-genesis:
image: tscel/exchain-node:latest
networks:
- exchain
volumes:
- "./layer2/deployments:/root/deployments"
- ".envrc:/root/.envrc"
entrypoint: opnode genesis l2 --deploy-config /root/deployments/config.json --l1-deployments /root/deployments/artifact.json --l2-allocs /root/deployments/alloc.json --outfile.l2 /root/deployments/genesis.json --outfile.rollup ./root/deployments/rollup.json --l1-rpc=http://172.80.1.2:8545
depends_on:
- forge-deploy
# exnode:
# image: tscel/exchain:v0.0.1
# networks:
# - exchain
# ports:
# - "1000:1000"
# logging:
# driver: "json-file"
# options:
# max-size: "100m"
# max-file: "3"
\ No newline at end of file
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369356)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967496)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564483)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076526)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 466947)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512629)
GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72624)
GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92973)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68433)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 68903)
GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155618)
\ No newline at end of file
# Artifacts and Cache
artifacts
forge-artifacts
cache
broadcast
kout-deployment
kout-proofs
test/kontrol/logs
# Metrics
coverage.out
.resource-metering.csv
# Testing State
.testdata
kontrol_prove_report.xml
# Scripts
scripts/go-ffi/go-ffi
# Environment Variables
.envrc
!.envrc.example
# Deployments
deployments/900
deployments/901
deployments/hardhat
deployments/getting-started
deployments/*/.deploy
deployments/1337
deployments/31337-deploy.json
deployments/kontrol.json
deployments/kontrol.jsonReversed
deployments/kontrol-fp.json
deployments/kontrol-fp.jsonReversed
# Devnet config which changes with each 'make devnet-up'
deploy-config/devnetL1.json
# Getting Started guide deploy config
deploy-config/getting-started.json
This diff is collapsed.
# Contributing to CONTRIBUTING.md
First off, thanks for taking the time to contribute!
We welcome and appreciate all kinds of contributions. We ask that before contributing you please review the procedures for each type of contribution available in the [Table of Contents](#table-of-contents). This will streamline the process for both maintainers and contributors. To find ways to contribute, view the [I Want To Contribute](#i-want-to-contribute) section below. Larger contributions should [open an issue](https://github.com/ethereum-optimism/optimism/issues/new) before implementation to ensure changes don't go to waste.
We're excited to work with you and your contributions to scaling Ethereum!
## Table of Contents
- [I Have a Question](#i-have-a-question)
- [I Want To Contribute](#i-want-to-contribute)
- [Reporting Bugs](#reporting-bugs)
- [Suggesting Enhancements](#suggesting-enhancements)
- [Your First Code Contribution](#your-first-code-contribution)
- [Improving The Documentation](#improving-the-documentation)
- [Deploying on Devnet](#deploying-on-devnet)
- [Tools](#tools)
## I Have a Question
> **Note**
> Before making an issue, please read the documentation and search the issues to see if your question has already been answered.
If you have any questions about the smart contracts, please feel free to ask them in the Optimism discord developer channels or create a new detailed issue.
## I Want To Contribute
### Reporting Bugs
**Any and all bug reports on production smart contract code should be submitted privately to the Optimism team so that we can mitigate the issue before it is exploited. Please see our security policy document [here](https://github.com/ethereum-optimism/.github/blob/master/SECURITY.md).**
### Suggesting Enhancements
#### Before Submitting an Enhancement
- Read the documentation and the smart contracts themselves to see if the feature already exists.
- Perform a search in the issues to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
#### How Do I Submit a Good Enhancement Suggestion?
Enhancement suggestions are tracked as [GitHub issues](/issues).
- Use a **clear and descriptive title** for the issue to identify the suggestion.
- Provide a **step-by-step** description of the suggested enhancement in as many details as possible.
- Describe the **current** behavior and why the **intended** behavior you expected to see differs. At this point you can also tell which alternatives do not work for you.
- Explain why this enhancement would be useful in Optimism's smart contracts. You may also want to point out the other projects that solved it better and which could serve as inspiration.
### Your First Code Contribution
The best place to begin contributing is by looking through the issues with the `good first issue` label. These are issues that are relatively easy to implement and are a great way to get familiar with the codebase.
Optimism's smart contracts are written in Solidity and we use [foundry](https://github.com/foundry-rs/foundry) as our development framework. To get started, you'll need to install several dependencies:
1. [just](https://github.com/casey/just)
1. Make sure to `just install`
1. [foundry](https://getfoundry.sh)
1. Foundry is built with [rust](https://www.rust-lang.org/tools/install), and this project uses a pinned version of foundry. Install the rust toolchain with `rustup`.
1. Make sure to install the version of foundry used by `ci-builder`, defined in the `versions.json` file in the root of this repo under the `foundry` key. Once you have `foundryup` installed, there is a helper to do this: `just install-foundry`
1. [golang](https://golang.org/doc/install)
1. [python](https://www.python.org/downloads/)
Our [Style Guide](STYLE_GUIDE.md) contains information about the project structure, syntax preferences, naming conventions, and more. Please take a look at it before submitting a PR, and let us know if you spot inconsistencies!
Once you've read the style guide and are ready to work on your PR, there are a plethora of useful `just` scripts to know about that will help you with development.
You can run `just -l` to list them all, some of the key ones are:
1. `just build` Builds the smart contracts.
1. `just test` Runs the full `forge` test suite.
1 `just gas-snapshot` Generates the gas snapshot for the smart contracts.
1. `just semver-lock` Generates the semver lockfile.
1. `just snapshots` Generates the storage and ABI snapshots.
1. `just autogen-invariant-docs` Generates the invariant test documentation.
1. `just clean` Removes all build artifacts for `forge` and `go` compilations.
1. `just validate-spacers` Validates the positions of the storage slot spacers.
1. `just validate-deploy-configs` Validates the deployment configurations in `deploy-config`
1. `just lint` Runs the linter on the smart contracts and scripts.
1. `just pre-pr` Runs most checks, generators, and linters prior to a PR. For most PRs, this is sufficient to pass CI if everything is in order.
1. `just pre-pr-full` Runs all checks, generators, and linters prior to a PR.
### Improving The Documentation
Documentation improvements are more than welcome! If you see a typo or feel that a code comment describes something poorly or incorrectly, please submit a PR with a fix.
### Deploying on Devnet
To deploy the smart contracts on a local devnet, run `make devnet-up` in the monorepo root. For more information on the local devnet, see [dev-node](https://docs.optimism.io/chain/testing/dev-node).
### Tools
#### Validate Spacing
In order to make sure that we don't accidentally overwrite storage slots, contract storage layouts are checked to make sure spacing is correct.
This uses the `snapshots/storageLayout` directory to check contract spacing. Run `just validate-spacers` to check the spacing of all contracts.
#### Gas Snapshots
We use forge's `gas-snapshot` subcommand to produce a gas snapshot for tests in `Benchmark.t.sol`. CI will check that the gas snapshot has been updated properly when it runs, so make sure to run `just gas-snapshot`!
#### Semver Locking
Many of our smart contracts are semantically versioned. To make sure that changes are not made to a contract without deliberately bumping its version, we commit to the source code and the creation bytecode of its dependencies in a lockfile. Consult the [Style Guide](./STYLE_GUIDE.md#Versioning) for more information about how our contracts are versioned.
#### Storage Snapshots
Due to the many proxied contracts in Optimism's protocol, we automate tracking the diff to storage layouts of the contracts in the project. This is to ensure that we don't break a proxy by upgrading its implementation to a contract with a different storage layout. To generate the storage lockfile, run `just snapshots`.
MIT License
Copyright (c) 2022 Optimism
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.
This diff is collapsed.
# Smart Contract Style Guide
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Standards and Conventions](#standards-and-conventions)
- [Style](#style)
- [Comments](#comments)
- [Errors](#errors)
- [Function Parameters](#function-parameters)
- [Function Return Arguments](#function-return-arguments)
- [Event Parameters](#event-parameters)
- [Immutable variables](#immutable-variables)
- [Spacers](#spacers)
- [Proxy by Default](#proxy-by-default)
- [Versioning](#versioning)
- [Exceptions](#exceptions)
- [Dependencies](#dependencies)
- [Source Code](#source-code)
- [Tests](#tests)
- [Expect Revert with Low Level Calls](#expect-revert-with-low-level-calls)
- [Organizing Principles](#organizing-principles)
- [Test function naming convention](#test-function-naming-convention)
- [Contract Naming Conventions](#contract-naming-conventions)
- [Withdrawing From Fee Vaults](#withdrawing-from-fee-vaults)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
This document provides guidance on how we organize and write our smart contracts. For cases where
this document does not provide guidance, please refer to existing contracts for guidance,
with priority on the `L2OutputOracle` and `OptimismPortal`.
## Standards and Conventions
### Style
#### Comments
Optimism smart contracts follow the triple-slash [solidity natspec comment style](https://docs.soliditylang.org/en/develop/natspec-format.html#documentation-example)
with additional rules. These are:
- Always use `@notice` since it has the same general effect as `@dev` but avoids confusion about when to use one over the other.
- Include a newline between `@notice` and the first `@param`.
- Include a newline between `@param` and the first `@return`.
- Use a line-length of 100 characters.
We also have the following custom tags:
- `@custom:proxied`: Add to a contract whenever it's meant to live behind a proxy.
- `@custom:upgradeable`: Add to a contract whenever it's meant to be inherited by an upgradeable contract.
- `@custom:semver`: Add to `version` variable which indicate the contracts semver.
- `@custom:legacy`: Add to an event or function when it only exists for legacy support.
- `@custom:network-specific`: Add to state variables which vary between OP Chains.
#### Errors
- Use `require` statements when making simple assertions.
- Use `revert(string)` if throwing an error where an assertion is not being made (no custom errors).
See [here](https://github.com/ethereum-optimism/optimism/blob/861ae315a6db698a8c0adb1f8eab8311fd96be4c/packages/contracts-bedrock/contracts/L2/OVM_ETH.sol#L31)
for an example of this in practice.
- Error strings MUST have the format `"{ContractName}: {message}"` where `message` is a lower case string.
#### Function Parameters
- Function parameters should be prefixed with an underscore.
#### Function Return Arguments
- Arguments returned by functions should be suffixed with an underscore.
#### Event Parameters
- Event parameters should NOT be prefixed with an underscore.
#### Immutable variables
Immutable variables:
- should be in `SCREAMING_SNAKE_CASE`
- should be `internal`
- should have a hand written getter function
This approach clearly indicates to the developer that the value is immutable, without exposing
the non-standard casing to the interface. It also ensures that we don’t need to break the ABIs if
we switch between values being in storage and immutable.
#### Spacers
We use spacer variables to account for old storage slots that are no longer being used.
The name of a spacer variable MUST be in the format `spacer_<slot>_<offset>_<length>` where
`<slot>` is the original storage slot number, `<offset>` is the original offset position
within the storage slot, and `<length>` is the original size of the variable.
Spacers MUST be `private`.
### Proxy by Default
All contracts should be assumed to live behind proxies (except in certain special circumstances).
This means that new contracts MUST be built under the assumption of upgradeability.
We use a minimal [`Proxy`](./src/universal/Proxy.sol) contract designed to be owned by a
corresponding [`ProxyAdmin`](./src/universal/ProxyAdmin.sol) which follow the interfaces
of OpenZeppelin's `Proxy` and `ProxyAdmin` contracts, respectively.
Unless explicitly discussed otherwise, you MUST include the following basic upgradeability
pattern for each new implementation contract:
1. Extend OpenZeppelin's `Initializable` base contract.
2. Include a function `initialize` with the modifier `initializer()`.
3. In the `constructor`:
1. Call `_disableInitializers()` to ensure the implementation contract cannot be initialized.
2. Set any immutables. However, we generally prefer to not use immutables to ensure the same implementation contracts can be used for all chains, and to allow chain operators to dynamically configure parameters
Because `reinitializer(uint64 version)` is not used, the process for upgrading the implementation is to atomically:
1. Upgrade the implementation to the `StorageSetter` contract.
2. Use that to set the initialized slot (typically slot 0) to zero.
3. Upgrade the implementation to the desired new implementation and `initialize` it.
### Versioning
All (non-library and non-abstract) contracts MUST inherit the `ISemver` interface which
exposes a `version()` function that returns a semver-compliant version string.
Contracts must have a `version` of `1.0.0` or greater to be production ready.
Additionally, contracts MUST use the following versioning scheme when incrementing their version:
- `patch` releases are to be used only for changes that do NOT modify contract bytecode (such as updating comments).
- `minor` releases are to be used for changes that modify bytecode OR changes that expand the contract ABI provided that these changes do NOT break the existing interface.
- `major` releases are to be used for changes that break the existing contract interface OR changes that modify the security model of a contract.
The remainder of the contract versioning and release process can be found in [`VERSIONING.md](./VERSIONING.md).
#### Exceptions
We have made an exception to the `Semver` rule for the `WETH` contract to avoid
making changes to a well-known, simple, and recognizable contract.
Additionally, bumping the patch version does change the bytecode, so another exception is carved out for this.
In other words, changing comments increments the patch version, which changes bytecode. This bytecode
change implies a minor version increment is needed, but because it's just a version change, only a
patch increment should be used.
### Dependencies
Where basic functionality is already supported by an existing contract in the OpenZeppelin library,
we should default to using the Upgradeable version of that contract.
### Source Code
The following guidelines should be followed for all contracts in the `src/` directory:
- All state changing functions should emit a corresponding event. This ensures that all actions are transparent, can be easily monitored, and can be reconstructed from the event logs.
### Tests
Tests are written using Foundry.
All test contracts and functions should be organized and named according to the following guidelines.
These guidelines are also encoded in a script which can be run with:
```
tsx scripts/checks/check-test-names.ts
```
#### Expect Revert with Low Level Calls
There is a non-intuitive behavior in foundry tests, which is documented [here](https://book.getfoundry.sh/cheatcodes/expect-revert?highlight=expectrevert#expectrevert).
When testing for a revert on a low-level call, please use the `revertsAsExpected` pattern suggested there.
_Note: This is a work in progress, not all test files are compliant with these guidelines._
#### Organizing Principles
- Solidity `contract`s are used to organize the test suite similar to how mocha uses describe.
- Every non-trivial state changing function should have a separate contract for happy and sad path
tests. This helps to make it very obvious where there are not yet sad path tests.
- Simpler functions like getters and setters are grouped together into test contracts.
#### Test function naming convention
Test function names are split by underscores, into 3 or 4 parts. An example function name is `test_onlyOwner_callerIsNotOwner_reverts()`.
The parts are: `[method]_[FunctionName]_[reason]_[status]`, where:
- `[method]` is either `test`, `testFuzz`, or `testDiff`
- `[FunctionName]` is the name of the function or higher level behavior being tested.
- `[reason]` is an optional description for the behavior being tested.
- `[status]` must be one of:
- `succeeds`: used for most happy path cases
- `reverts`: used for most sad path cases
- `works`: used for tests which include a mix of happy and sad assertions (these should be broken up if possible)
- `fails`: used for tests which 'fail' in some way other than reverting
- `benchmark`: used for tests intended to establish gas costs
#### Contract Naming Conventions
Test contracts should be named one of the following according to their use:
- `TargetContract_Init` for contracts that perform basic setup to be reused in other test contracts.
- `TargetContract_Function_Test` for contracts containing happy path tests for a given function.
- `TargetContract_Function_TestFail` for contracts containing sad path tests for a given function.
To minimize clutter, getter functions can be grouped together into a single test contract,
ie. `TargetContract_Getters_Test`.
## Withdrawing From Fee Vaults
See the file `scripts/FeeVaultWithdrawal.s.sol` to withdraw from the L2 fee vaults. It includes
instructions on how to run it. `foundry` is required.
This diff is collapsed.
#!/bin/bash
target=/app/deployments
basedir=/app/contracts
tmptarget=$basedir/deployments
source /app/.envrc
rm -rf $tmptarget
cp -r $target $basedir/
ls $basedir/scripts/getting-started
cd $basedir && ./scripts/getting-started/config.sh
cd $basedir && \
DEPLOYMENT_OUTFILE=$tmptarget/artifact.json \
DEPLOY_CONFIG_PATH=$tmptarget/config.json \
forge script scripts/deploy/Deploy.s.sol:Deploy \
--non-interactive \
--broadcast --private-key $PRIVATE_KEY \
--rpc-url $L1_RPC_URL
cp $tmptarget/* $target/
\ No newline at end of file
{
"l1StartingBlockTag": "0x94422278aec355e06b362b12118df275e0a7153cac694495781413f3c19bf071",
"l1ChainID": 10099,
"l2ChainID": 42069,
"l2BlockTime": 2,
"l1BlockTime": 12,
"maxSequencerDrift": 600,
"sequencerWindowSize": 3600,
"channelTimeout": 300,
"p2pSequencerAddress": "0xDCAD5Fe85AfC4857d6c638d37eb0948791878069",
"batchInboxAddress": "0xff00000000000000000000000000000000042069",
"batchSenderAddress": "0x74FB49FB24700C896B6E68Af0dB872Ac0cD97c0C",
"l2OutputOracleSubmissionInterval": 120,
"l2OutputOracleStartingBlockNumber": 0,
"l2OutputOracleStartingTimestamp": 1741852709,
"l2OutputOracleProposer": "0x123463a4B065722E99115D6c222f267d9cABb524",
"l2OutputOracleChallenger": "0x88395111AB1586a4030dAC62a183542762929bbC",
"finalizationPeriodSeconds": 12,
"proxyAdminOwner": "0x88395111AB1586a4030dAC62a183542762929bbC",
"baseFeeVaultRecipient": "0x88395111AB1586a4030dAC62a183542762929bbC",
"l1FeeVaultRecipient": "0x88395111AB1586a4030dAC62a183542762929bbC",
"sequencerFeeVaultRecipient": "0x88395111AB1586a4030dAC62a183542762929bbC",
"finalSystemOwner": "0x88395111AB1586a4030dAC62a183542762929bbC",
"superchainConfigGuardian": "0x88395111AB1586a4030dAC62a183542762929bbC",
"baseFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000",
"l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000",
"sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000",
"baseFeeVaultWithdrawalNetwork": 0,
"l1FeeVaultWithdrawalNetwork": 0,
"sequencerFeeVaultWithdrawalNetwork": 0,
"gasPriceOracleOverhead": 0,
"gasPriceOracleScalar": 1000000,
"enableGovernance": true,
"governanceTokenSymbol": "OP",
"governanceTokenName": "Optimism",
"governanceTokenOwner": "0x88395111AB1586a4030dAC62a183542762929bbC",
"l2GenesisBlockGasLimit": "0x1c9c380",
"l2GenesisBlockBaseFeePerGas": "0x3b9aca00",
"eip1559Denominator": 50,
"eip1559DenominatorCanyon": 250,
"eip1559Elasticity": 6,
"l2GenesisFjordTimeOffset": "0x0",
"l2GenesisRegolithTimeOffset": "0x0",
"l2GenesisEcotoneTimeOffset": "0x0",
"l2GenesisDeltaTimeOffset": "0x0",
"l2GenesisCanyonTimeOffset": "0x0",
"systemConfigStartBlock": 0,
"requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000",
"recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000",
"faultGameAbsolutePrestate": "0x03c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98",
"faultGameMaxDepth": 44,
"faultGameClockExtension": 0,
"faultGameMaxClockDuration": 600,
"faultGameGenesisBlock": 0,
"faultGameGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"faultGameSplitDepth": 14,
"faultGameWithdrawalDelay": 604800,
"preimageOracleMinProposalSize": 1800000,
"preimageOracleChallengePeriod": 86400,
"proofMaturityDelaySeconds": 12,
"disputeGameFinalityDelaySeconds": 6,
"respectedGameType": 0,
"useFaultProofs": false,
"fundDevAccounts": true,
"useAltDA": false,
"daChallengeWindow": 100,
"daResolveWindow": 100,
"daBondSize": 1000,
"daResolverRefundPercentage": 50,
"useCustomGasToken": false,
"customGasTokenAddress": "0x88395111AB1586a4030dAC62a183542762929bbC"
}
################################################################
# PROFILE: DEFAULT (Local) #
################################################################
[profile.default]
# Compilation settings
src = 'src'
out = 'forge-artifacts'
script = 'scripts'
optimizer = true
optimizer_runs = 999999
remappings = [
'@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts',
'@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts',
'@openzeppelin/contracts-v5/=lib/openzeppelin-contracts-v5/contracts',
'@rari-capital/solmate/=lib/solmate',
'@lib-keccak/=lib/lib-keccak/contracts/lib',
'@solady/=lib/solady/src',
'forge-std/=lib/forge-std/src',
'ds-test/=lib/forge-std/lib/ds-test/src',
'safe-contracts/=lib/safe-contracts/contracts',
'kontrol-cheatcodes/=lib/kontrol-cheatcodes/src',
'gelato/=lib/automate/contracts'
]
extra_output = ['devdoc', 'userdoc', 'metadata', 'storageLayout']
bytecode_hash = 'none'
build_info_path = 'artifacts/build-info'
ast = true
evm_version = "cancun"
# 5159 error code is selfdestruct error code
ignored_error_codes = ["transient-storage", "code-size", "init-code-size", 5159]
# We set the gas limit to max int64 to avoid running out of gas during testing, since the default
# gas limit is 1B and some of our tests require more gas than that, such as `test_callWithMinGas_noLeakageLow_succeeds`.
# We use this gas limit since it was the default gas limit prior to https://github.com/foundry-rs/foundry/pull/8274.
# Due to toml-rs limitations, if you increase the gas limit above this value it must be a string.
gas_limit = 9223372036854775807
# Test / Script Runner Settings
ffi = true
fs_permissions = [
{ access='read-write', path='./.resource-metering.csv' },
{ access='read-write', path='./snapshots/' },
{ access='read-write', path='./deployments/' },
{ access='read', path='./deploy-config/' },
{ access='read', path='./deploy-config-periphery/' },
{ access='read', path='./broadcast/' },
{ access='read', path = './forge-artifacts/' },
{ access='write', path='./semver-lock.json' },
{ access='read-write', path='./.testdata/' },
{ access='read', path='./kout-deployment' }
]
libs = ["node_modules", "lib"]
[fuzz]
runs = 64
[fmt]
line_length=120
multiline_func_header='all'
bracket_spacing=true
wrap_comments=true
################################################################
# PROFILE: CI #
################################################################
[profile.ci]
fuzz = { runs = 512 }
[profile.ci.invariant]
runs = 256
depth = 32
################################################################
# PROFILE: LITE #
################################################################
[profile.lite]
optimizer = false
################################################################
# PROFILE: KONTROL #
################################################################
# See test/kontrol/README.md for an explanation of how the profiles are configured
[profile.kdeploy]
src = 'src/L1'
out = 'kout-deployment'
test = 'test/kontrol'
script = 'scripts-kontrol'
[profile.kprove]
src = 'test/kontrol/proofs'
out = 'kout-proofs'
test = 'test/kontrol/proofs'
script = 'test/kontrol/proofs'
prebuild:
./scripts/checks/check-foundry-install.sh
dep-status:
git submodule status
install:
forge install
build: prebuild
forge build
build-go-ffi:
cd scripts/go-ffi && go build
autogen-invariant-docs:
go run ./scripts/autogen/generate-invariant-docs .
test: build-go-ffi
forge test
test-kontrol:
./test/kontrol/scripts/run-kontrol.sh script
test-rerun: build-go-ffi
forge test --rerun -vvv
genesis:
forge script scripts/L2Genesis.s.sol:L2Genesis --sig 'runWithStateDump()'
coverage: build-go-ffi
forge coverage || (bash -c "forge coverage 2>&1 | grep -q 'Stack too deep' && echo -e '\\033[1;33mWARNING\\033[0m: Coverage failed with stack too deep, so overriding and exiting successfully' && exit 0 || exit 1")
coverage-lcov: build-go-ffi
forge coverage --report lcov || (bash -c "forge coverage --report lcov 2>&1 | grep -q 'Stack too deep' && echo -e '\\033[1;33mWARNING\\033[0m: Coverage failed with stack too deep, so overriding and exiting successfully' && exit 0 || exit 1")
deploy:
./scripts/deploy/deploy.sh
gas-snapshot-no-build:
forge snapshot --match-contract GasBenchMark
statediff:
./scripts/utils/statediff.sh && git diff --exit-code
gas-snapshot: build-go-ffi gas-snapshot-no-build
gas-snapshot-check: build-go-ffi
forge snapshot --match-contract GasBenchMark --check
kontrol-summary:
./test/kontrol/scripts/make-summary-deployment.sh
kontrol-summary-fp:
KONTROL_FP_DEPLOYMENT=true ./test/kontrol/scripts/make-summary-deployment.sh
snapshots-abi-storage:
go run ./scripts/autogen/generate-snapshots .
snapshots: build snapshots-no-build
snapshots-no-build: snapshots-abi-storage kontrol-summary-fp kontrol-summary
snapshots-check:
./scripts/checks/check-snapshots.sh
semver-lock:
forge script scripts/autogen/SemverLock.s.sol
validate-deploy-configs:
./scripts/checks/check-deploy-configs.sh
validate-spacers-no-build:
go run ./scripts/checks/spacers
validate-spacers: build validate-spacers-no-build
clean:
rm -rf ./artifacts ./forge-artifacts ./cache ./scripts/go-ffi/go-ffi ./.testdata ./deployments/hardhat/*
pre-pr-no-build: gas-snapshot-no-build snapshots-no-build semver-lock autogen-invariant-docs lint
pre-pr: clean build-go-ffi build pre-pr-no-build
pre-pr-full: test validate-deploy-configs validate-spacers pre-pr
lint-forge-tests-check:
go run ./scripts/checks/names
lint-contracts-check:
forge fmt --check
lint-check: lint-contracts-check
lint-contracts-fix:
forge fmt
lint-fix: lint-contracts-fix
lint: lint-fix lint-check
version: 2.1 # use CircleCI 2.1
#orbs:
#coveralls: coveralls/coveralls@1.0.6
jobs: # a collection of steps
build: # runs not using Workflows must have a `build` job as entry point
working_directory: ~/gelato-instadapp-ci # directory where steps will run
docker: # run the steps with Docker
- image: circleci/node:14.15.3 # ...with this image as the primary container; this is where all `steps` will run
steps: # a collection of executable commands
- checkout # special step to check out source code to working directory
- restore_cache: # restore the dependency cache
# Read about caching dependencies: https://circleci.com/docs/2.0/caching/
name: Restore Yarn Package Cache
key: yarn-packages-{{ checksum "yarn.lock" }}
- run:
name: yarn install
command: yarn install --frozen-lockfile
- save_cache: # special step to save the dependency cache
name: Save Yarn Package Cache
key: yarn-packages-{{ checksum "yarn.lock" }}
paths:
- ./node_modules
- restore_cache: # restore hardhat compile cache
name: Restore Hardhat Compilation Cache
key: solidity-files-cache-
- run: # Compile
name: Compile
command: yarn compile
- save_cache: # special step to save the hardhat compile cache
name: Save Hardhat Compilation Cache
key: solidity-files-cache-{{ checksum "./cache/solidity-files-cache.json" }}
paths:
- ./cache/solidity-files-cache.json
- run: # Formatting
name: Prettier Check
command: yarn prettier --check .
- run: # Linting
name: ESLint
command: yarn lint
- restore_cache: # restore the Hardhat Network Fork Cache
name: Restore Hardhat Network Fork Cache
key: v4-hardhat-network-fork-cache
- run: # Tests with Gas reporter
name: Tests using hardhat mainnet fork and gas reporter
command: yarn test:gas
- save_cache: # special step to save the Hardhat Network Fork cache
name: Save Hardhat Network Fork Cache
key: v4-hardhat-network-fork-cache
paths:
- ./cache/hardhat-network-fork
- run: # Codechecks
name: Codechecks gas reporting
command: npx codechecks
#- run: # Coverage
#name: solidity-coverage
#command: yarn hardhat coverage
#- coveralls/upload # upload lcov.info to coveralls.io
# - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/
# path: coverage
# prefix: coverage
# See https://circleci.com/docs/2.0/deployment-integrations/ for deploy examples
cache/
coverage/
typechain
\ No newline at end of file
{
"env": {
"commonjs": true,
"es2021": true,
"node": true,
"mocha": true
},
"extends": ["eslint:recommended", "prettier"],
"parserOptions": {
"ecmaVersion": 12
},
// Typescript config
"overrides": [
{
"files": ["*.ts"],
"parser": "@typescript-eslint/parser",
"parserOptions": { "project": "./tsconfig.json" },
"plugins": ["@typescript-eslint", "prettier"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/eslint-recommended",
"prettier"
],
"rules": {
"prettier/prettier": "error",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "default",
"format": ["camelCase"]
},
{
"selector": "variable",
"format": ["camelCase", "UPPER_CASE"]
},
{
"selector": "parameter",
"format": ["camelCase"],
"leadingUnderscore": "allow"
},
{
"selector": ["objectLiteralProperty"],
"format": ["camelCase", "PascalCase"]
},
{
"selector": ["classProperty"],
"modifiers": ["private", "static"],
"format": ["PascalCase", "UPPER_CASE"]
},
{
"selector": ["classProperty", "classMethod"],
"modifiers": ["private"],
"format": ["camelCase"],
"leadingUnderscore": "require"
},
{
"selector": ["classProperty", "classMethod"],
"modifiers": ["protected"],
"format": ["camelCase"],
"leadingUnderscore": "require"
},
{
"selector": "typeLike",
"format": ["PascalCase"]
},
{
"selector": ["enumMember"],
"format": ["camelCase", "PascalCase"]
},
{
"selector": "variable",
"types": ["boolean"],
"format": ["PascalCase"],
"prefix": ["is", "should", "has", "can", "did", "will"]
}
]
}
}
]
}
# Dependency directory
node_modules
# local env variables
.env
#hardhat
artifacts/
cache/
artifacts-zk/
cache-zk/
# foundry
artifacts-foundry
foundry_cache
docs
!broadcast
broadcast/*
broadcast/*/31337/
.gas-snapshot
# Typechain generation
typechain
# ESLint
.eslintcache
# macOS
.DS_Store
*.icloud
# yarn
yarn-error.log
package-lock.json
dist
\ No newline at end of file
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/prb-test"]
path = lib/prb-test
url = https://github.com/PaulRBerg/prb-test
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn install && yarn build && yarn lint-staged && yarn lint:sol
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn test
# Ignore:
artifacts
cache
dist
coverage
typechain
yarn.lock
yarn-error.log
{
"overrides": [
{
"files": "*.sol",
"options": {
"compiler": "0.8.14"
}
}
]
}
{
"extends": "solhint:recommended",
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "error",
"code-complexity": ["error", 5],
"function-max-lines": ["error", 40],
"max-line-length": ["error", 100],
"max-states-count": ["warn", 3],
"no-empty-blocks": "error",
"no-unused-vars": "error",
"payable-fallback": "off",
"reason-string": ["off", { "maxLength": 32 }],
"constructor-syntax": "off",
"comprehensive-interface": "off",
"quotes": ["error", "double"],
"const-name-snakecase": "error",
"contract-name-camelcase": "error",
"event-name-camelcase": "error",
"func-name-mixedcase": "error",
"func-param-name-mixedcase": "error",
"modifier-name-mixedcase": "error",
"private-vars-leading-underscore": ["error", { "strict": false }],
"var-name-mixedcase": "error",
"imports-on-top": "error",
"ordering": "error",
"visibility-modifier-order": "error",
"avoid-call-value": "off",
"avoid-low-level-calls": "off",
"avoid-sha3": "error",
"avoid-suicide": "error",
"avoid-throw": "error",
"avoid-tx-origin": "off",
"check-send-result": "error",
"compiler-version": ["error", "^0.8.0"],
"mark-callable-contracts": "off",
"func-visibility": ["error", { "ignoreConstructors": true }],
"multiple-sends": "error",
"no-complex-fallback": "error",
"no-inline-assembly": "off",
"not-rely-on-block-hash": "error",
"not-rely-on-time": "error",
"reentrancy": "error",
"state-visibility": "error"
}
}
node_modules/
contracts/interfaces
contracts/vendor
\ No newline at end of file
{
"solidity.compileUsingRemoteVersion": "latest",
"solidity.enableLocalNodeCompiler": false,
"solidity.formatter": "prettier",
"solidity.linter": "solhint",
"solidity.packageDefaultDependenciesContractsDirectory": "",
"solidity.packageDefaultDependenciesDirectory": "node_modules",
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
# Gelato Functions 🍦
> ℹ️ If you are looking for the Automate Legacy Smart Contracts version,
> they are available on the [`legacy` branch](https://github.com/gelatodigital/automate/tree/legacy).
## Description
Automate smart contract executions with Gelato by submitting tasks to `Gelato Functions`
Check out the [Gelato Documentation](https://docs.gelato.network/).
checks:
- name: eth-gas-reporter/codechecks
settings:
branches:
- master
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.14;
import {
EnumerableSet
} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {Gelatofied} from "./vendor/gelato/Gelatofied.sol";
import {GelatoBytes} from "./vendor/gelato/GelatoBytes.sol";
import {Proxied} from "./vendor/proxy/EIP173/Proxied.sol";
import {AutomateStorage} from "./AutomateStorage.sol";
import {LibDataTypes} from "./libraries/LibDataTypes.sol";
import {LibEvents} from "./libraries/LibEvents.sol";
import {LibTaskId} from "./libraries/LibTaskId.sol";
import {LibTaskModule} from "./libraries/LibTaskModule.sol";
import {LibBypassModule} from "./libraries/LibBypassModule.sol";
import {IAutomate} from "./interfaces/IAutomate.sol";
/**
* @notice Automate enables everyone to have Gelato monitor and execute transactions.
* @notice ExecAddress refers to the contract that has the function which Gelato will call.
* @notice Modules allow users to customise conditions and specifications when creating a task.
*/
//solhint-disable function-max-lines
//solhint-disable no-empty-blocks
contract Automate is Gelatofied, Proxied, AutomateStorage, IAutomate {
using GelatoBytes for bytes;
using EnumerableSet for EnumerableSet.Bytes32Set;
// solhint-disable const-name-snakecase
string public constant version = "7";
constructor(address payable _gelato) Gelatofied(_gelato) {}
///@inheritdoc IAutomate
function createTask(
address _execAddress,
bytes calldata _execDataOrSelector,
LibDataTypes.ModuleData calldata _moduleData,
address _feeToken
) external override returns (bytes32 taskId) {
address taskCreator;
(taskCreator, _execAddress) = LibTaskModule.preCreateTask(
msg.sender,
_execAddress,
taskModuleAddresses
);
taskId = _createTask(
taskCreator,
_execAddress,
_execDataOrSelector,
_moduleData,
_feeToken
);
}
///@inheritdoc IAutomate
function cancelTask(bytes32 _taskId) external {
address _taskCreator = LibTaskModule.preCancelTask(
_taskId,
msg.sender,
taskModuleAddresses
);
_cancelTask(_taskCreator, _taskId);
}
///@inheritdoc IAutomate
function exec(
address _taskCreator,
address _execAddress,
bytes memory _execData,
LibDataTypes.ModuleData calldata _moduleData,
uint256 _txFee,
address _feeToken,
bool _revertOnFailure
) external onlyGelato {
bytes32 taskId = LibTaskId.getTaskId(
_taskCreator,
_execAddress,
_execData.memorySliceSelector(),
_moduleData,
_feeToken
);
require(
_createdTasks[_taskCreator].contains(taskId),
"Automate.exec: Task not found"
);
fee = _txFee;
feeToken = _feeToken;
bool success = LibTaskModule.onExecTask(
taskId,
_taskCreator,
_execAddress,
_execData,
_moduleData.modules,
_revertOnFailure,
taskModuleAddresses
);
delete fee;
delete feeToken;
emit LibEvents.ExecSuccess(
_txFee,
_feeToken,
_execAddress,
_execData,
taskId,
success
);
}
///@inheritdoc IAutomate
function exec1Balance(
address _taskCreator,
address _execAddress,
bytes memory _execData,
LibDataTypes.ModuleData calldata _moduleData,
Gelato1BalanceParam calldata _oneBalanceParam,
bool _revertOnFailure
) external onlyGelato {
bytes32 taskId = LibTaskId.getTaskId(
_taskCreator,
_execAddress,
_execData.memorySliceSelector(),
_moduleData,
address(0)
);
require(
_createdTasks[_taskCreator].contains(taskId),
"Automate.exec: Task not found"
);
bool success = LibTaskModule.onExecTask(
taskId,
_taskCreator,
_execAddress,
_execData,
_moduleData.modules,
_revertOnFailure,
taskModuleAddresses
);
emit LibEvents.ExecSuccess(
0,
address(0),
_execAddress,
_execData,
taskId,
success
);
emit LogUseGelato1Balance(
_oneBalanceParam.sponsor,
_execAddress,
_oneBalanceParam.feeToken,
_oneBalanceParam.oneBalanceChainId,
_oneBalanceParam.nativeToFeeTokenXRateNumerator,
_oneBalanceParam.nativeToFeeTokenXRateDenominator,
_oneBalanceParam.correlationId
);
}
function execBypassModuleSyncFee(
address _taskCreator,
address _execAddress,
bytes32 _taskId,
uint256 _txFee,
address _feeToken,
bytes memory _execData,
bool _revertOnFailure,
bool _singleExec
) external onlyGelato {
require(
_createdTasks[_taskCreator].contains(_taskId),
"Automate.exec: Task not found"
);
fee = _txFee;
feeToken = _feeToken;
bool success = LibBypassModule.onExecTask(
_taskId,
_taskCreator,
_execAddress,
_execData,
_revertOnFailure,
_singleExec,
_createdTasks
);
delete fee;
delete feeToken;
emit LibEvents.ExecBypassModuleSyncFeeSuccess(
_taskId,
_txFee,
_feeToken,
success
);
}
///@inheritdoc IAutomate
function execBypassModule(
address _taskCreator,
address _execAddress,
bytes32 _taskId,
bytes32 _correlationId,
bytes memory _execData,
bool _revertOnFailure,
bool _singleExec
) external onlyGelato {
require(
_createdTasks[_taskCreator].contains(_taskId),
"Automate.exec: Task not found"
);
bool success = LibBypassModule.onExecTask(
_taskId,
_taskCreator,
_execAddress,
_execData,
_revertOnFailure,
_singleExec,
_createdTasks
);
emit LibEvents.ExecBypassModuleSuccess(
_taskId,
_correlationId,
success
);
}
///@inheritdoc IAutomate
function setModule(
LibDataTypes.Module[] calldata _modules,
address[] calldata _moduleAddresses
) external onlyProxyAdmin {
uint256 length = _modules.length;
for (uint256 i; i < length; i++) {
taskModuleAddresses[_modules[i]] = _moduleAddresses[i];
}
}
///@inheritdoc IAutomate
function getFeeDetails() external view returns (uint256, address) {
return (fee, feeToken);
}
///@inheritdoc IAutomate
function getTaskIdsByUser(address _taskCreator)
external
view
returns (bytes32[] memory)
{
bytes32[] memory taskIds = _createdTasks[_taskCreator].values();
return taskIds;
}
///@inheritdoc IAutomate
function getTaskId(
address taskCreator,
address execAddress,
bytes4 execSelector,
LibDataTypes.ModuleData memory moduleData,
address feeToken
) external pure returns (bytes32 taskId) {
taskId = LibTaskId.getTaskId(
taskCreator,
execAddress,
execSelector,
moduleData,
feeToken
);
}
function _createTask(
address _taskCreator,
address _execAddress,
bytes memory _execDataOrSelector,
LibDataTypes.ModuleData memory _moduleData,
address _feeToken
) private returns (bytes32 taskId) {
taskId = LibTaskId.getTaskId(
_taskCreator,
_execAddress,
_execDataOrSelector.memorySliceSelector(),
_moduleData,
_feeToken
);
require(
!_createdTasks[_taskCreator].contains(taskId),
"Automate.createTask: Duplicate task"
);
LibTaskModule.onCreateTask(
taskId,
_taskCreator,
_execAddress,
_execDataOrSelector,
_moduleData,
taskModuleAddresses
);
_createdTasks[_taskCreator].add(taskId);
emit LibEvents.TaskCreated(
_taskCreator,
_execAddress,
_execDataOrSelector,
_moduleData,
_feeToken,
taskId
);
}
function _cancelTask(address _taskCreator, bytes32 _taskId) private {
require(
_createdTasks[_taskCreator].contains(_taskId),
"Automate.cancelTask: Task not found"
);
_createdTasks[_taskCreator].remove(_taskId);
emit LibEvents.TaskCancelled(_taskId, _taskCreator);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;
import {
EnumerableSet
} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {LibDataTypes} from "./libraries/LibDataTypes.sol";
/**
* @notice Storage layout of Automate smart contract.
*/
// solhint-disable max-states-count
abstract contract AutomateStorage {
mapping(bytes32 => address) public taskCreator; ///@dev Deprecated
mapping(bytes32 => address) public execAddresses; ///@dev Deprecated
mapping(address => EnumerableSet.Bytes32Set) internal _createdTasks;
uint256 public fee;
address public feeToken;
///@dev Appended State
mapping(bytes32 => LibDataTypes.Time) public timedTask; ///@dev Deprecated
mapping(LibDataTypes.Module => address) public taskModuleAddresses;
mapping(bytes32 => uint256) public nonce1Balance; ///@dev Deprecated
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
import {GelatoBytes} from "../vendor/gelato/GelatoBytes.sol";
// solhint-disable private-vars-leading-underscore
// solhint-disable func-visibility
function _call(
address _add,
bytes memory _data,
uint256 _value,
bool _revertOnFailure,
string memory _tracingInfo
) returns (bool success, bytes memory returnData) {
(success, returnData) = _add.call{value: _value}(_data);
if (!success && _revertOnFailure)
GelatoBytes.revertWithError(returnData, _tracingInfo);
}
function _delegateCall(
address _add,
bytes memory _data,
string memory _tracingInfo
) returns (bool success, bytes memory returnData) {
(success, returnData) = _add.delegatecall(_data);
if (!success) GelatoBytes.revertWithError(returnData, _tracingInfo);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;
import {
SafeERC20,
IERC20
} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
address constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
// solhint-disable private-vars-leading-underscore
// solhint-disable func-visibility
function _transfer(
address payable _to,
address _paymentToken,
uint256 _amount
) {
if (_paymentToken == ETH) {
(bool success, ) = _to.call{value: _amount}("");
require(success, "_transfer: ETH transfer failed");
} else {
SafeERC20.safeTransfer(IERC20(_paymentToken), _to, _amount);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.14;
import "./Types.sol";
abstract contract AutomateModuleHelper {
function _resolverModuleArg(
address _resolverAddress,
bytes memory _resolverData
) internal pure returns (bytes memory) {
return abi.encode(_resolverAddress, _resolverData);
}
function _proxyModuleArg() internal pure returns (bytes memory) {
return bytes("");
}
function _singleExecModuleArg() internal pure returns (bytes memory) {
return bytes("");
}
function _web3FunctionModuleArg(
string memory _web3FunctionHash,
bytes memory _web3FunctionArgsHex
) internal pure returns (bytes memory) {
return abi.encode(_web3FunctionHash, _web3FunctionArgsHex);
}
function _timeTriggerModuleArg(uint128 _start, uint128 _interval)
internal
pure
returns (bytes memory)
{
bytes memory triggerConfig = abi.encode(_start, _interval);
return abi.encode(TriggerType.TIME, triggerConfig);
}
function _cronTriggerModuleArg(string memory _expression)
internal
pure
returns (bytes memory)
{
bytes memory triggerConfig = abi.encode(_expression);
return abi.encode(TriggerType.CRON, triggerConfig);
}
function _eventTriggerModuleArg(
address _address,
bytes32[][] memory _topics,
uint256 _blockConfirmations
) internal pure returns (bytes memory) {
bytes memory triggerConfig = abi.encode(
_address,
_topics,
_blockConfirmations
);
return abi.encode(TriggerType.EVENT, triggerConfig);
}
function _blockTriggerModuleArg() internal pure returns (bytes memory) {
bytes memory triggerConfig = abi.encode(bytes(""));
return abi.encode(TriggerType.BLOCK, triggerConfig);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.14;
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "./Types.sol";
/**
* @dev Inherit this contract to allow your smart contract to
* - Make synchronous fee payments.
* - Have call restrictions for functions to be automated.
*/
// solhint-disable private-vars-leading-underscore
abstract contract AutomateReady {
IAutomate public immutable automate;
address public immutable dedicatedMsgSender;
address private immutable feeCollector;
address internal constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/**
* @dev
* Only tasks created by _taskCreator defined in constructor can call
* the functions with this modifier.
*/
modifier onlyDedicatedMsgSender() {
require(msg.sender == dedicatedMsgSender, "Only dedicated msg.sender");
_;
}
/**
* @dev
* _taskCreator is the address which will create tasks for this contract.
*/
constructor(address _automate, address _taskCreator) {
automate = IAutomate(_automate);
IGelato gelato = IGelato(IAutomate(_automate).gelato());
feeCollector = gelato.feeCollector();
address proxyModuleAddress = IAutomate(_automate).taskModuleAddresses(
Module.PROXY
);
address opsProxyFactoryAddress = IProxyModule(proxyModuleAddress)
.opsProxyFactory();
(dedicatedMsgSender, ) = IOpsProxyFactory(opsProxyFactoryAddress)
.getProxyOf(_taskCreator);
}
/**
* @dev
* Transfers fee to gelato for synchronous fee payments.
*
* _fee & _feeToken should be queried from IAutomate.getFeeDetails()
*/
function _transfer(uint256 _fee, address _feeToken) internal {
if (_feeToken == ETH) {
(bool success, ) = feeCollector.call{value: _fee}("");
require(success, "_transfer: ETH transfer failed");
} else {
SafeERC20.safeTransfer(IERC20(_feeToken), feeCollector, _fee);
}
}
function _getFeeDetails()
internal
view
returns (uint256 fee, address feeToken)
{
(fee, feeToken) = automate.getFeeDetails();
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "./Types.sol";
import {
Initializable
} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
/**
* @dev Inherit this contract to allow your upgradeable smart contract to
* - Make synchronous fee payments.
* - Have call restrictions for functions to be automated.
*/
//solhint-disable func-name-mixedcase
// solhint-disable private-vars-leading-underscore
abstract contract AutomateReadyUpgradeable is Initializable {
IAutomate public immutable automate;
address internal constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
address internal feeCollector;
address public dedicatedMsgSender;
/**
* @dev
* Only tasks created by _taskCreator defined in constructor can call
* the functions with this modifier.
*/
modifier onlyDedicatedMsgSender() {
require(msg.sender == dedicatedMsgSender, "Only dedicated msg.sender");
_;
}
constructor(address _automate) {
automate = IAutomate(_automate);
}
/**
* @dev
* _taskCreator is the address which will create tasks for this contract.
*/
function __AutomateReady_init(address _taskCreator)
internal
onlyInitializing
{
IGelato gelato = IGelato(automate.gelato());
feeCollector = gelato.feeCollector();
address proxyModuleAddress = IAutomate(automate).taskModuleAddresses(
Module.PROXY
);
address opsProxyFactoryAddress = IProxyModule(proxyModuleAddress)
.opsProxyFactory();
(dedicatedMsgSender, ) = IOpsProxyFactory(opsProxyFactoryAddress)
.getProxyOf(_taskCreator);
}
/**
* @dev
* Transfers fee to gelato for synchronous fee payments.
*
* _fee & _feeToken should be queried from IAutomate.getFeeDetails()
*/
function _transfer(uint256 _fee, address _feeToken) internal {
if (_feeToken == ETH) {
(bool success, ) = feeCollector.call{value: _fee}("");
require(success, "_transfer: ETH transfer failed");
} else {
SafeERC20.safeTransfer(IERC20(_feeToken), feeCollector, _fee);
}
}
function _getFeeDetails()
internal
view
returns (uint256 fee, address feeToken)
{
(fee, feeToken) = automate.getFeeDetails();
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.14;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./AutomateReady.sol";
import {AutomateModuleHelper} from "./AutomateModuleHelper.sol";
/**
* @dev Inherit this contract to allow your smart contract
* to be a task creator and create tasks.
*/
//solhint-disable const-name-snakecase
//solhint-disable no-empty-blocks
abstract contract AutomateTaskCreator is AutomateModuleHelper, AutomateReady {
using SafeERC20 for IERC20;
IGelato1Balance public constant gelato1Balance =
IGelato1Balance(0x7506C12a824d73D9b08564d5Afc22c949434755e);
constructor(address _automate) AutomateReady(_automate, address(this)) {}
function _depositFunds1Balance(
uint256 _amount,
address _token,
address _sponsor
) internal {
if (_token == ETH) {
///@dev Only deposit ETH on goerli for now.
require(block.chainid == 5, "Only deposit ETH on goerli");
gelato1Balance.depositNative{value: _amount}(_sponsor);
} else {
///@dev Only deposit USDC on polygon for now.
require(
block.chainid == 137 &&
_token ==
address(0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174),
"Only deposit USDC on polygon"
);
IERC20(_token).approve(address(gelato1Balance), _amount);
gelato1Balance.depositToken(_sponsor, _token, _amount);
}
}
function _createTask(
address _execAddress,
bytes memory _execDataOrSelector,
ModuleData memory _moduleData,
address _feeToken
) internal returns (bytes32) {
return
automate.createTask(
_execAddress,
_execDataOrSelector,
_moduleData,
_feeToken
);
}
function _cancelTask(bytes32 _taskId) internal {
automate.cancelTask(_taskId);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.14;
import "./AutomateReadyUpgradeable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @dev Inherit this contract to allow your upgradeable smart contract
* to be a task creator and create tasks.
*/
//solhint-disable func-name-mixedcase
//solhint-disable const-name-snakecase
//solhint-disable no-empty-blocks
abstract contract AutomateTaskCreatorUpgradeable is AutomateReadyUpgradeable {
using SafeERC20 for IERC20;
IGelato1Balance public constant gelato1Balance =
IGelato1Balance(0x7506C12a824d73D9b08564d5Afc22c949434755e);
constructor(address _automate) AutomateReadyUpgradeable(_automate) {}
function __AutomateTaskCreator_init() internal onlyInitializing {
__AutomateReady_init(address(this));
}
function _depositFunds1Balance(
uint256 _amount,
address _token,
address _sponsor
) internal {
if (_token == ETH) {
///@dev Only deposit ETH on goerli for now.
require(block.chainid == 5, "Only deposit ETH on goerli");
gelato1Balance.depositNative{value: _amount}(_sponsor);
} else {
///@dev Only deposit USDC on polygon for now.
require(
block.chainid == 137 &&
_token ==
address(0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174),
"Only deposit USDC on polygon"
);
IERC20(_token).approve(address(gelato1Balance), _amount);
gelato1Balance.depositToken(_sponsor, _token, _amount);
}
}
function _createTask(
address _execAddress,
bytes memory _execDataOrSelector,
ModuleData memory _moduleData,
address _feeToken
) internal returns (bytes32) {
return
automate.createTask(
_execAddress,
_execDataOrSelector,
_moduleData,
_feeToken
);
}
function _cancelTask(bytes32 _taskId) internal {
automate.cancelTask(_taskId);
}
function _resolverModuleArg(
address _resolverAddress,
bytes memory _resolverData
) internal pure returns (bytes memory) {
return abi.encode(_resolverAddress, _resolverData);
}
function _proxyModuleArg() internal pure returns (bytes memory) {
return bytes("");
}
function _singleExecModuleArg() internal pure returns (bytes memory) {
return bytes("");
}
function _web3FunctionModuleArg(
string memory _web3FunctionHash,
bytes memory _web3FunctionArgsHex
) internal pure returns (bytes memory) {
return abi.encode(_web3FunctionHash, _web3FunctionArgsHex);
}
function _timeTriggerModuleArg(uint128 _start, uint128 _interval)
internal
pure
returns (bytes memory)
{
bytes memory triggerConfig = abi.encode(_start, _interval);
return abi.encode(TriggerType.TIME, triggerConfig);
}
function _cronTriggerModuleArg(string memory _expression)
internal
pure
returns (bytes memory)
{
bytes memory triggerConfig = abi.encode(_expression);
return abi.encode(TriggerType.CRON, triggerConfig);
}
function _blockTriggerModuleArg() internal pure returns (bytes memory) {
bytes memory triggerConfig = abi.encode(bytes(""));
return abi.encode(TriggerType.BLOCK, triggerConfig);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;
enum Module {
RESOLVER,
DEPRECATED_TIME,
PROXY,
SINGLE_EXEC,
WEB3_FUNCTION,
TRIGGER
}
enum TriggerType {
TIME,
CRON,
EVENT,
BLOCK
}
struct ModuleData {
Module[] modules;
bytes[] args;
}
interface IAutomate {
function createTask(
address execAddress,
bytes calldata execDataOrSelector,
ModuleData calldata moduleData,
address feeToken
) external returns (bytes32 taskId);
function cancelTask(bytes32 taskId) external;
function getFeeDetails() external view returns (uint256, address);
function gelato() external view returns (address payable);
function taskModuleAddresses(Module) external view returns (address);
}
interface IProxyModule {
function opsProxyFactory() external view returns (address);
}
interface IOpsProxyFactory {
function getProxyOf(address account) external view returns (address, bool);
}
interface IGelato1Balance {
function depositNative(address _sponsor) external payable;
function depositToken(
address _sponsor,
address _token,
uint256 _amount
) external;
}
interface IGelato {
function feeCollector() external view returns (address);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "../../../AutomateTaskCreator.sol";
/**
* @dev
* Example contract that creates a resolver task.
*/
// solhint-disable not-rely-on-time
// solhint-disable no-empty-blocks
contract CounterResolverTaskCreator is AutomateTaskCreator {
uint256 public count;
uint256 public lastExecuted;
bytes32 public taskId;
uint256 public constant MAX_COUNT = 5;
uint256 public constant INTERVAL = 3 minutes;
event CounterTaskCreated(bytes32 taskId);
constructor(address _automate) AutomateTaskCreator(_automate) {}
function createTask() external {
require(taskId == bytes32(""), "Already started task");
ModuleData memory moduleData = ModuleData({
modules: new Module[](2),
args: new bytes[](2)
});
moduleData.modules[0] = Module.RESOLVER;
moduleData.modules[1] = Module.PROXY;
moduleData.args[0] = _resolverModuleArg(
address(this),
abi.encodeCall(this.checker, ())
);
moduleData.args[1] = _proxyModuleArg();
bytes32 id = _createTask(
address(this),
abi.encode(this.increaseCount.selector),
moduleData,
address(0)
);
taskId = id;
emit CounterTaskCreated(id);
}
function increaseCount(uint256 _amount) external onlyDedicatedMsgSender {
uint256 newCount = count + _amount;
if (newCount >= MAX_COUNT) {
_cancelTask(taskId);
count = 0;
} else {
count += _amount;
lastExecuted = block.timestamp;
}
}
function checker()
external
view
returns (bool canExec, bytes memory execPayload)
{
canExec = (block.timestamp - lastExecuted) >= INTERVAL;
execPayload = abi.encodeCall(this.increaseCount, (1));
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "../../../AutomateTaskCreator.sol";
/**
* @dev
* Example contract that creates a single exec task.
*/
// solhint-disable not-rely-on-time
// solhint-disable no-empty-blocks
contract CounterSingleExecTaskCreator is AutomateTaskCreator {
uint256 public count;
uint256 public lastExecuted;
bytes32 public taskId;
uint256 public constant MAX_COUNT = 5;
uint256 public constant INTERVAL = 3 minutes;
event CounterTaskCreated(bytes32 taskId);
constructor(address _automate) AutomateTaskCreator(_automate) {}
function createTask() external {
require(taskId == bytes32(""), "Already started task");
bytes memory execData = abi.encodeCall(this.increaseCount, (1));
ModuleData memory moduleData = ModuleData({
modules: new Module[](2),
args: new bytes[](2)
});
moduleData.modules[0] = Module.PROXY;
moduleData.modules[1] = Module.SINGLE_EXEC;
moduleData.args[0] = _proxyModuleArg();
moduleData.args[1] = _singleExecModuleArg();
bytes32 id = _createTask(
address(this),
execData,
moduleData,
address(0)
);
taskId = id;
emit CounterTaskCreated(id);
}
function increaseCount(uint256 _amount) external onlyDedicatedMsgSender {
count += _amount;
taskId = bytes32("");
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "../../../AutomateTaskCreator.sol";
/**
* @dev
* Example contract that creates a time task.
*/
// solhint-disable not-rely-on-time
// solhint-disable no-empty-blocks
contract CounterTriggerTaskCreator is AutomateTaskCreator {
uint256 public count;
uint256 public lastExecuted;
bytes32 public taskId;
uint256 public constant MAX_COUNT = 5;
uint128 public constant INTERVAL = 3 minutes;
event CounterTaskCreated(bytes32 taskId);
constructor(address _automate) AutomateTaskCreator(_automate) {}
function createTask() external {
require(taskId == bytes32(""), "Already started task");
bytes memory execData = abi.encodeCall(this.increaseCount, (1));
ModuleData memory moduleData = ModuleData({
modules: new Module[](2),
args: new bytes[](2)
});
moduleData.modules[0] = Module.TRIGGER;
moduleData.modules[1] = Module.PROXY;
moduleData.args[0] = _timeTriggerModuleArg(
uint128(block.timestamp),
INTERVAL
);
moduleData.args[1] = _proxyModuleArg();
bytes32 id = _createTask(
address(this),
execData,
moduleData,
address(0)
);
taskId = id;
emit CounterTaskCreated(id);
}
function increaseCount(uint256 _amount) external onlyDedicatedMsgSender {
uint256 newCount = count + _amount;
if (newCount >= MAX_COUNT) {
_cancelTask(taskId);
count = 0;
} else {
count += _amount;
lastExecuted = block.timestamp;
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
import "../../../AutomateTaskCreator.sol";
//solhint-disable no-empty-blocks
//solhint-disable not-rely-on-time
contract CounterWeb3Function is AutomateTaskCreator {
uint256 public count;
uint256 public lastExecuted;
bytes32 public taskId;
uint256 public constant MAX_COUNT = 5;
uint256 public constant INTERVAL = 3 minutes;
event CounterTaskCreated(bytes32 taskId);
constructor(address _automate) AutomateTaskCreator(_automate) {}
function createTask(
string memory _web3FunctionHash,
bytes calldata _web3FunctionArgsHex
) external {
require(taskId == bytes32(""), "Already started task");
bytes memory execData = abi.encodeCall(this.increaseCount, (1));
ModuleData memory moduleData = ModuleData({
modules: new Module[](2),
args: new bytes[](2)
});
moduleData.modules[0] = Module.PROXY;
moduleData.modules[1] = Module.WEB3_FUNCTION;
moduleData.args[0] = _proxyModuleArg();
moduleData.args[1] = _web3FunctionModuleArg(
_web3FunctionHash,
_web3FunctionArgsHex
);
bytes32 id = _createTask(
address(this),
execData,
moduleData,
address(0)
);
taskId = id;
emit CounterTaskCreated(id);
}
function cancelTask() external {
require(taskId != bytes32(""), "Task not started");
_cancelTask(taskId);
}
function increaseCount(uint256 _amount) external onlyDedicatedMsgSender {
uint256 newCount = count + _amount;
if (newCount >= MAX_COUNT) {
_cancelTask(taskId);
count = 0;
} else {
count += _amount;
lastExecuted = block.timestamp;
}
}
function depositFunds(uint256 _amount, address _token) external payable {
_depositFunds1Balance(_amount, _token, address(this));
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "../../../AutomateTaskCreator.sol";
/**
* @dev
* Example contract that creates a resolver task.
*/
// solhint-disable not-rely-on-time
// solhint-disable no-empty-blocks
contract CounterResolverTaskCreatorWT is AutomateTaskCreator {
uint256 public count;
uint256 public lastExecuted;
bytes32 public taskId;
uint256 public constant MAX_COUNT = 5;
uint256 public constant INTERVAL = 3 minutes;
event CounterTaskCreated(bytes32 taskId);
constructor(address payable _automate) AutomateTaskCreator(_automate) {}
receive() external payable {}
function createTask() external payable {
require(taskId == bytes32(""), "Already started task");
ModuleData memory moduleData = ModuleData({
modules: new Module[](2),
args: new bytes[](2)
});
moduleData.modules[0] = Module.RESOLVER;
moduleData.modules[1] = Module.PROXY;
moduleData.args[0] = _resolverModuleArg(
address(this),
abi.encodeCall(this.checker, ())
);
moduleData.args[1] = _proxyModuleArg();
bytes32 id = _createTask(
address(this),
abi.encode(this.increaseCount.selector),
moduleData,
ETH
);
taskId = id;
emit CounterTaskCreated(id);
}
function increaseCount(uint256 _amount) external onlyDedicatedMsgSender {
uint256 newCount = count + _amount;
if (newCount >= MAX_COUNT) {
_cancelTask(taskId);
count = 0;
} else {
count += _amount;
lastExecuted = block.timestamp;
}
(uint256 fee, address feeToken) = _getFeeDetails();
_transfer(fee, feeToken);
}
function checker()
external
view
returns (bool canExec, bytes memory execPayload)
{
canExec = (block.timestamp - lastExecuted) >= INTERVAL;
execPayload = abi.encodeCall(this.increaseCount, (1));
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "../../../AutomateTaskCreator.sol";
/**
* @dev
* Example contract that creates a single exec task.
*/
// solhint-disable not-rely-on-time
// solhint-disable no-empty-blocks
contract CounterSingleExecTaskCreatorWT is AutomateTaskCreator {
uint256 public count;
uint256 public lastExecuted;
bytes32 public taskId;
uint256 public constant MAX_COUNT = 5;
uint256 public constant INTERVAL = 3 minutes;
event CounterTaskCreated(bytes32 taskId);
constructor(address payable _automate) AutomateTaskCreator(_automate) {}
receive() external payable {}
function createTask() external payable {
require(taskId == bytes32(""), "Already started task");
bytes memory execData = abi.encodeCall(this.increaseCount, (1));
ModuleData memory moduleData = ModuleData({
modules: new Module[](2),
args: new bytes[](2)
});
moduleData.modules[0] = Module.PROXY;
moduleData.modules[1] = Module.SINGLE_EXEC;
moduleData.args[0] = _proxyModuleArg();
moduleData.args[1] = _singleExecModuleArg();
bytes32 id = _createTask(address(this), execData, moduleData, ETH);
taskId = id;
emit CounterTaskCreated(id);
}
function increaseCount(uint256 _amount) external onlyDedicatedMsgSender {
count += _amount;
taskId = bytes32("");
(uint256 fee, address feeToken) = _getFeeDetails();
_transfer(fee, feeToken);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "../../../AutomateTaskCreator.sol";
/**
* @dev
* Example contract that creates a trigger task.
*/
// solhint-disable not-rely-on-time
// solhint-disable no-empty-blocks
contract CounterTriggerTaskCreatorWT is AutomateTaskCreator {
uint256 public count;
uint256 public lastExecuted;
bytes32 public taskId;
uint256 public constant MAX_COUNT = 5;
uint128 public constant INTERVAL = 3 minutes;
event CounterTaskCreated(bytes32 taskId);
constructor(address payable _automate) AutomateTaskCreator(_automate) {}
receive() external payable {}
function createTask() external payable {
require(taskId == bytes32(""), "Already started task");
bytes memory execData = abi.encodeCall(this.increaseCount, (1));
ModuleData memory moduleData = ModuleData({
modules: new Module[](2),
args: new bytes[](2)
});
moduleData.modules[0] = Module.TRIGGER;
moduleData.modules[1] = Module.PROXY;
moduleData.args[0] = _timeTriggerModuleArg(
uint128(block.timestamp),
INTERVAL
);
moduleData.args[1] = _proxyModuleArg();
bytes32 id = _createTask(address(this), execData, moduleData, ETH);
taskId = id;
emit CounterTaskCreated(id);
}
function increaseCount(uint256 _amount) external onlyDedicatedMsgSender {
uint256 newCount = count + _amount;
if (newCount >= MAX_COUNT) {
_cancelTask(taskId);
count = 0;
} else {
count += _amount;
lastExecuted = block.timestamp;
}
(uint256 fee, address feeToken) = _getFeeDetails();
_transfer(fee, feeToken);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import {AutomateReady} from "../../../AutomateReady.sol";
// solhint-disable not-rely-on-time
// solhint-disable no-empty-blocks
contract Counter is AutomateReady {
uint256 public count;
uint256 public lastExecuted;
constructor(address payable _automate, address _taskCreator)
AutomateReady(_automate, _taskCreator)
{}
function increaseCount(uint256 amount) external onlyDedicatedMsgSender {
count += amount;
lastExecuted = block.timestamp;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
interface ICounter {
function increaseCount(uint256 amount) external;
function lastExecuted() external view returns (uint256);
}
// solhint-disable not-rely-on-time
contract CounterResolver {
ICounter public immutable counter;
constructor(ICounter _counter) {
counter = _counter;
}
function checker()
external
view
returns (bool canExec, bytes memory execPayload)
{
uint256 lastExecuted = counter.lastExecuted();
canExec = (block.timestamp - lastExecuted) > 180;
execPayload = abi.encodeCall(ICounter.increaseCount, (1));
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
interface ICounter {
function increaseCount(uint256 amount) external;
function lastExecuted() external view returns (uint256);
}
// solhint-disable not-rely-on-time
contract CounterResolverWT {
ICounter public immutable counter;
constructor(ICounter _counter) {
counter = _counter;
}
function checker()
external
view
returns (bool canExec, bytes memory execPayload)
{
uint256 lastExecuted = counter.lastExecuted();
canExec = (block.timestamp - lastExecuted) > 180;
execPayload = abi.encodeCall(ICounter.increaseCount, (1));
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import {AutomateReady} from "../../../AutomateReady.sol";
// solhint-disable not-rely-on-time
// solhint-disable no-empty-blocks
contract CounterWT is AutomateReady {
uint256 public count;
uint256 public lastExecuted;
constructor(address _automate, address _taskCreator)
AutomateReady(_automate, _taskCreator)
{}
receive() external payable {}
function increaseCount(uint256 amount) external onlyDedicatedMsgSender {
count += amount;
lastExecuted = block.timestamp;
(uint256 fee, address feeToken) = _getFeeDetails();
_transfer(fee, feeToken);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {LibDataTypes} from "../libraries/LibDataTypes.sol";
import {IGelato1Balance} from "./IGelato1Balance.sol";
// solhint-disable max-line-length
interface IAutomate is IGelato1Balance {
/**
* @notice Initiates a task with conditions which Gelato will monitor and execute when conditions are met.
*
* @param execAddress Address of contract that should be called by Gelato.
* @param execData Execution data to be called with / function selector if execution data is yet to be determined.
* @param moduleData Conditional modules that will be used. {See LibDataTypes-ModuleData}
* @param feeToken Address of token to be used as payment. Use address(0) if Gelato 1Balance is being used, 0xeeeeee... for ETH or native tokens.
*
* @return taskId Unique hash of the task created.
*/
function createTask(
address execAddress,
bytes calldata execData,
LibDataTypes.ModuleData calldata moduleData,
address feeToken
) external returns (bytes32 taskId);
/**
* @notice Terminates a task that was created and Gelato can no longer execute it.
*
* @param taskId Unique hash of the task that is being cancelled. {See LibTaskId-getTaskId}
*/
function cancelTask(bytes32 taskId) external;
/**
* @notice Execution API called by Gelato, using Sync Fee as fee payment method
*
* @param taskCreator The address which created the task.
* @param execAddress Address of contract that should be called by Gelato.
* @param execData Execution data to be called with / function selector if execution data is yet to be determined.
* @param moduleData Conditional modules that will be used. {See LibDataTypes-ModuleData}
* @param txFee Fee paid to Gelato for execution, transfered to Gelato.feeCollector().
* @param feeToken Token used to pay for the execution. ETH = 0xeeeeee...
* @param revertOnFailure To revert or not if call to execAddress fails. (Used for off-chain simulations)
*/
function exec(
address taskCreator,
address execAddress,
bytes memory execData,
LibDataTypes.ModuleData calldata moduleData,
uint256 txFee,
address feeToken,
bool revertOnFailure
) external;
/**
* @notice Execution API called by Gelato, using Gelato 1Balance as fee payment method.
*
* @param taskCreator The address which created the task.
* @param execAddress Address of contract that should be called by Gelato.
* @param execData Execution data to be called with / function selector if execution data is yet to be determined.
* @param moduleData Conditional modules that will be used. {See LibDataTypes-ModuleData}
* @param oneBalanceParam Parameters required for fee payment with Gelato 1Balance.
* @param revertOnFailure To revert or not if call to execAddress fails. (Used for off-chain simulations)
*/
function exec1Balance(
address taskCreator,
address execAddress,
bytes memory execData,
LibDataTypes.ModuleData calldata moduleData,
Gelato1BalanceParam calldata oneBalanceParam,
bool revertOnFailure
) external;
/**
* @notice Execution API called by Gelato, using Gelato 1Balance as fee payment method.
*
* @param taskCreator The address which created the task.
* @param execAddress Address of contract that should be called by Gelato.
* @param taskId Unique hash of the task.
* @param correlationId Id of the execution to be used for 1Balance settlement.
* @param execData Execution data to be called with / function selector if execution data is yet to be determined.
* @param revertOnFailure To revert or not if call to execAddress fails. (Used for off-chain simulations)
* @param singleExec If the task is a SingleExec task. If true, task will be cancelled after execution.
*/
function execBypassModule(
address taskCreator,
address execAddress,
bytes32 taskId,
bytes32 correlationId,
bytes memory execData,
bool revertOnFailure,
bool singleExec
) external;
/**
* @notice Execution API called by Gelato, using Gelato Sync fee as fee payment method.
*
* @param taskCreator The address which created the task.
* @param execAddress Address of contract that should be called by Gelato.
* @param taskId Unique hash of the task.
* @param txFee Fee paid to Gelato for execution, transfered to Gelato.feeCollector().
* @param feeToken Token used to pay for the execution. ETH = 0xeeeeee...
* @param execData Execution data to be called with / function selector if execution data is yet to be determined.
* @param revertOnFailure To revert or not if call to execAddress fails. (Used for off-chain simulations)
* @param singleExec If the task is a SingleExec task. If true, task will be cancelled after execution.
*/
function execBypassModuleSyncFee(
address taskCreator,
address execAddress,
bytes32 taskId,
uint256 txFee,
address feeToken,
bytes memory execData,
bool revertOnFailure,
bool singleExec
) external;
/**
* @notice Sets the address of task modules. Only callable by proxy admin.
*
* @param modules List of modules to be set
* @param moduleAddresses List of addresses for respective modules.
*/
function setModule(
LibDataTypes.Module[] calldata modules,
address[] calldata moduleAddresses
) external;
/**
* @notice Helper function to query fee and feeToken to be used for payment. (For executions which pays itself)
*
* @return uint256 Fee amount to be paid.
* @return address Token to be paid. (Determined and passed by taskCreator during createTask)
*/
function getFeeDetails() external view returns (uint256, address);
/**
* @notice Helper func to query all open tasks by a task creator.
*
* @param taskCreator Address of task creator to query.
*
* @return bytes32[] List of taskIds created.
*/
function getTaskIdsByUser(address taskCreator)
external
view
returns (bytes32[] memory);
/**
* @notice Helper function to compute task id with module arguments
*
* @param taskCreator The address which created the task.
* @param execAddress Address of contract that will be called by Gelato.
* @param execSelector Signature of the function which will be called by Gelato.
* @param moduleData Conditional modules that will be used. {See LibDataTypes-ModuleData}
* @param feeToken Address of token to be used as payment. Use address(0) if Gelato 1Balance is being used, 0xeeeeee... for ETH or native tokens.
*/
function getTaskId(
address taskCreator,
address execAddress,
bytes4 execSelector,
LibDataTypes.ModuleData memory moduleData,
address feeToken
) external pure returns (bytes32 taskId);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20Extended {
function decimals() external view returns (uint256);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender)
external
view
returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IGelato1Balance {
struct Gelato1BalanceParam {
address sponsor;
address feeToken;
uint256 oneBalanceChainId;
uint256 nativeToFeeTokenXRateNumerator;
uint256 nativeToFeeTokenXRateDenominator;
bytes32 correlationId;
}
event LogUseGelato1Balance(
address indexed sponsor,
address indexed target,
address indexed feeToken,
uint256 oneBalanceChainId,
uint256 nativeToFeeTokenXRateNumerator,
uint256 nativeToFeeTokenXRateDenominator,
bytes32 correlationId
);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
interface IOpsProxy {
/**
* @notice Emitted when proxy calls a contract successfully in `executeCall`
*
* @param target Address of contract that is called
* @param data Data used in the call.
* @param value Native token value used in the call.
* @param returnData Data returned by the call.
*/
event ExecuteCall(
address indexed target,
bytes data,
uint256 value,
bytes returnData
);
/**
* @notice Multicall to different contracts with different datas.
*
* @param targets Addresses of contracts to be called.
* @param datas Datas for each contract call.
* @param values Native token value for each contract call.
*/
function batchExecuteCall(
address[] calldata targets,
bytes[] calldata datas,
uint256[] calldata values
) external payable;
/**
* @notice Call to a single contract.
*
* @param target Address of contracts to be called.
* @param data Data for contract call.
* @param value Native token value for contract call.
*/
function executeCall(
address target,
bytes calldata data,
uint256 value
) external payable;
/**
* @return address Ops smart contract address
*/
function ops() external view returns (address);
/**
* @return address Owner of the proxy
*/
function owner() external view returns (address);
/**
* @return uint256 version of OpsProxy.
*/
function version() external view returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
interface IOpsProxyFactory {
/**
* @notice Emitted when an OpsProxy is deployed.
*
* @param deployer Address which initiated the deployment
* @param owner The address which the proxy is for.
* @param proxy Address of deployed proxy.
*/
event DeployProxy(
address indexed deployer,
address indexed owner,
address indexed proxy
);
/**
* @notice Emitted when OpsProxy implementation to be deployed is changed.
*
* @param oldImplementation Previous OpsProxy implementation.
* @param newImplementation Current OpsProxy implementation.
*/
event SetImplementation(
address indexed oldImplementation,
address indexed newImplementation
);
/**
* @notice Emitted when OpsProxy implementation is added or removed from whitelist.
*
* @param implementation OpsProxy implementation.
* @param whitelisted Added or removed from whitelist.
*/
event UpdateWhitelistedImplementation(
address indexed implementation,
bool indexed whitelisted
);
/**
* @notice Deploys OpsProxy for the msg.sender.
*
* @return proxy Address of deployed proxy.
*/
function deploy() external returns (address payable proxy);
/**
* @notice Deploys OpsProxy for another address.
*
* @param owner Address to deploy the proxy for.
*
* @return proxy Address of deployed proxy.
*/
function deployFor(address owner) external returns (address payable proxy);
/**
* @notice Sets the OpsProxy implementation that will be deployed by OpsProxyFactory.
*
* @param newImplementation New implementation to be set.
*/
function setImplementation(address newImplementation) external;
/**
* @notice Add or remove OpsProxy implementation from the whitelist.
*
* @param implementation OpsProxy implementation.
* @param whitelist Added or removed from whitelist.
*/
function updateWhitelistedImplementations(
address implementation,
bool whitelist
) external;
/**
* @notice Determines the OpsProxy address when it is not deployed.
*
* @param account Address to determine the proxy address for.
*/
function determineProxyAddress(address account)
external
view
returns (address);
/**
* @return address Proxy address owned by account.
* @return bool Whether if proxy is deployed
*/
function getProxyOf(address account) external view returns (address, bool);
/**
* @return address Owner of deployed proxy.
*/
function ownerOf(address proxy) external view returns (address);
/**
* @return bool Whether if implementation is whitelisted.
*/
function whitelistedImplementations(address implementation)
external
view
returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @notice Standard interface that should be used when creating a resolver.
*
* NOTE `checker` can be non view as well as it will be
* called with static call off chain
*/
interface IResolver {
function checker()
external
view
returns (bool canExec, bytes memory execPayload);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
// solhint-disable max-line-length
interface ITaskModule {
/**
* @notice Called before generating taskId.
* @dev Modules can override execAddress or taskCreator. {See ProxyModule-preCreateTask}
*
* @param taskCreator The address which created the task.
* @param execAddress Address of contract that should be called.
*
* @return address Overriden or original taskCreator.
* @return address Overriden or original execAddress.
*/
function preCreateTask(address taskCreator, address execAddress)
external
returns (address, address);
/**
* @notice Initiates task module whenever `createTask` is being called.
*
* @param taskId Unique hash of the task created.
* @param taskCreator The address which created the task.
* @param execAddress Address of contract that should be called.
* @param execData Execution data to be called with / function selector if execution data is yet to be determined.
* @param initModuleArg Encoded arguments for module if any.
*/
function onCreateTask(
bytes32 taskId,
address taskCreator,
address execAddress,
bytes calldata execData,
bytes calldata initModuleArg
) external;
/**
* @notice Called before taskId is removed from _createdTasks[].
* @dev Modules can override taskCreator.
*
* @param taskId Unique hash of the task created.
* @param taskCreator The address which created the task.
*
* @return address Overriden or original taskCreator.
*/
function preCancelTask(bytes32 taskId, address taskCreator)
external
returns (address);
/**
* @notice Called during `exec` and before execAddress is called.
*
* @param taskId Unique hash of the task created.
* @param taskCreator The address which created the task.
* @param execAddress Address of contract that should be called.
* @param execData Execution data to be called with / function selector if execution data is yet to be determined.
*
* @return address Overriden or original execution address.
* @return bytes Overriden or original execution data.
*/
function preExecCall(
bytes32 taskId,
address taskCreator,
address execAddress,
bytes calldata execData
) external returns (address, bytes memory);
/**
* @notice Called during `exec` and after execAddress is called.
*
* @param taskId Unique hash of the task created.
* @param taskCreator The address which created the task.
* @param execAddress Address of contract that should be called.
* @param execData Execution data to be called with / function selector if execution data is yet to be determined.
*/
function postExecCall(
bytes32 taskId,
address taskCreator,
address execAddress,
bytes calldata execData
) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
import {
EnumerableSet
} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {_call, _delegateCall} from "../functions/FExec.sol";
import {LibDataTypes} from "./LibDataTypes.sol";
import {LibEvents} from "./LibEvents.sol";
import {LibTaskModule} from "./LibTaskModule.sol";
import {LibTaskModuleConfig} from "./LibTaskModuleConfig.sol";
import {ITaskModule} from "../interfaces/ITaskModule.sol";
// solhint-disable function-max-lines
/// @notice Simplified library for task executions
library LibBypassModule {
using EnumerableSet for EnumerableSet.Bytes32Set;
using LibTaskModuleConfig for LibDataTypes.Module;
/**
* @notice Delegate calls SingleExecModule on exec for single exec tasks.
*
* @param _taskId Unique hash of the task. {See LibTaskId-getTaskId}
* @param _taskCreator Address which created the task.
* @param _execAddress Address of contract that will be called by Gelato.
* @param _execData Execution data to be called with / function selector.
* @param _revertOnFailure To revert or not if call to execAddress fails.
* @param _singleExec If task is a single exec task.
* @param _createdTasks The storage reference of owner to the taskIds created mapping.
*/
function onExecTask(
bytes32 _taskId,
address _taskCreator,
address _execAddress,
bytes memory _execData,
bool _revertOnFailure,
bool _singleExec,
mapping(address => EnumerableSet.Bytes32Set) storage _createdTasks
) internal returns (bool callSuccess) {
(callSuccess, ) = _call(
_execAddress,
abi.encodePacked(_execData, _taskCreator),
0,
_revertOnFailure,
"Automate.exec: "
);
if (_singleExec) {
_createdTasks[_taskCreator].remove(_taskId);
emit LibEvents.TaskCancelled(_taskId, _taskCreator);
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
// solhint-disable max-line-length
library LibDataTypes {
/**
* @notice Whitelisted modules that are available for users to customise conditions and specifications of their tasks.
*
* @param RESOLVER Use dynamic condition & input data for execution. {See ResolverModule.sol}
* @param DEPRECATED_TIME deprecated
* @param PROXY Creates a dedicated caller (msg.sender) to be used when executing the task. {See ProxyModule.sol}
* @param SINGLE_EXEC Task is cancelled after one execution. {See SingleExecModule.sol}
* @param WEB3_FUNCTION Use off-chain condition & input data for execution. {See Web3FunctionModule.sol}
* @param TRIGGER Repeated execution of task ata a specified timing and interval or cron. {See TriggerModule.sol}
*/
enum Module {
RESOLVER,
DEPRECATED_TIME, // @deprecated
PROXY,
SINGLE_EXEC,
WEB3_FUNCTION,
TRIGGER
}
/**
* @notice Struct to contain modules and their relative arguments that are used for task creation.
*
* @param modules List of selected modules.
* @param args Arguments of modules if any. Pass "0x" for modules which does not require args {See encodeModuleArg}
*/
struct ModuleData {
Module[] modules;
bytes[] args;
}
/**
* @notice Struct for time module.
*
* @param nextExec Time when the next execution should occur.
* @param interval Time interval between each execution.
*/
struct Time {
uint128 nextExec;
uint128 interval;
}
/**
* @notice Types of trigger
*
* @param TIME Time triggered tasks, starting at a specific time and triggered intervally
* @param CRON Cron triggered tasks, triggered according to the cron conditions
*/
enum TriggerType {
TIME,
CRON,
EVENT,
BLOCK
}
/**
* @notice Struct for trigger module
*
* @param triggerType Type of the trigger
* @param triggerConfig Trigger configuration that shuold be parsed according to triggerType
*/
struct TriggerModuleData {
TriggerType triggerType;
bytes triggerConfig;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
import {LibDataTypes} from "./LibDataTypes.sol";
library LibEvents {
/**
* @notice Emitted when `createTask` is called.
*
* @param taskCreator The address which created the task.
* @param execAddress Address of contract that is called by Gelato.
* @param execDataOrSelector Execution data / function selector.
* @param moduleData Conditional modules used. {See LibDataTypes-ModuleData}
* @param feeToken Token used to pay for the execution. ETH = 0xeeeeee...
* @param taskId Unique hash of the task. {See LibTaskId-getTaskId}
*/
event TaskCreated(
address indexed taskCreator,
address indexed execAddress,
bytes execDataOrSelector,
LibDataTypes.ModuleData moduleData,
address feeToken,
bytes32 indexed taskId
);
/**
* @notice Emitted when `cancelTask` is called.
*
* @param taskId Unique hash of the task. {See LibTaskId-getTaskId}
* @param taskCreator The address which owned the task.
*/
event TaskCancelled(bytes32 taskId, address taskCreator);
/**
* @notice Emitted when `exec` is called.
*
* @param txFee Fee paid to Gelato for execution
* @param feeToken Token used to pay for the execution. ETH = 0xeeeeee...
* @param execAddress Address of contract that will be called by Gelato.
* @param execData Execution data / function selector.
* @param taskId Unique hash of the task. {See LibTaskId-getTaskId}
* @param callSuccess Status of the call to execAddress.
*/
event ExecSuccess(
uint256 indexed txFee,
address indexed feeToken,
address indexed execAddress,
bytes execData,
bytes32 taskId,
bool callSuccess
);
/**
* @notice Emitted when `execBypassModule` is called.
*
* @param taskId Unique hash of the task. {See LibTaskId-getTaskId}
* @param correlationId Id of the execution to be used for 1Balance settlement.
* @param callSuccess Status of the call to execAddress.
*/
event ExecBypassModuleSuccess(
bytes32 taskId,
bytes32 correlationId,
bool callSuccess
);
/**
* @notice Emitted when `execBypassModuleSyncFee` is called.
*
* @param taskId Unique hash of the task. {See LibTaskId-getTaskId}
* @param txFee Fee paid to Gelato for execution
* @param feeToken Token used to pay for the execution. ETH = 0xeeeeee...
* @param callSuccess Status of the call to execAddress.
*/
event ExecBypassModuleSyncFeeSuccess(
bytes32 taskId,
uint256 txFee,
address feeToken,
bool callSuccess
);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import {LibDataTypes} from "./LibDataTypes.sol";
/**
* @notice Library to compute taskId of tasks.
*/
// solhint-disable max-line-length
library LibTaskId {
/**
* @notice Returns taskId of taskCreator.
*
* @param taskCreator The address which created the task.
* @param execAddress Address of contract that will be called by Gelato.
* @param execSelector Signature of the function which will be called by Gelato.
* @param moduleData Conditional modules that will be used. {See LibDataTypes-ModuleData}
* @param feeToken Address of token to be used as payment. Use address(0) if Gelato 1Balance is being used, 0xeeeeee... for ETH or native tokens.
*/
function getTaskId(
address taskCreator,
address execAddress,
bytes4 execSelector,
LibDataTypes.ModuleData memory moduleData,
address feeToken
) internal pure returns (bytes32 taskId) {
taskId = keccak256(
abi.encode(
taskCreator,
execAddress,
execSelector,
moduleData,
feeToken
)
);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
import {LibDataTypes} from "./LibDataTypes.sol";
/**
* @notice Library to determine wether to call task modules to reduce unnecessary calls.
*/
library LibTaskModuleConfig {
function requirePreCreate(LibDataTypes.Module _module)
internal
pure
returns (bool)
{
if (_module == LibDataTypes.Module.PROXY) return true;
return false;
}
function requirePreCancel(LibDataTypes.Module _module)
internal
pure
returns (bool)
{
if (_module == LibDataTypes.Module.PROXY) return true;
return false;
}
function requireOnCreate(LibDataTypes.Module _module)
internal
pure
returns (bool)
{
if (_module == LibDataTypes.Module.PROXY) return true;
return false;
}
function requirePreExec(LibDataTypes.Module _module)
internal
pure
returns (bool)
{
if (_module == LibDataTypes.Module.PROXY) return true;
return false;
}
function requirePostExec(LibDataTypes.Module _module)
internal
pure
returns (bool)
{
if (_module == LibDataTypes.Module.SINGLE_EXEC) return true;
return false;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;
import {Proxied} from "../vendor/proxy/EIP173/Proxied.sol";
import {_call} from "../functions/FExec.sol";
import {IOpsProxy} from "../interfaces/IOpsProxy.sol";
contract OpsProxy is Proxied, IOpsProxy {
// solhint-disable const-name-snakecase
uint256 public constant override version = 1;
address public immutable override ops;
modifier onlyAuth() {
address proxyOwner = owner();
if (msg.sender != proxyOwner) {
require(msg.sender == ops, "OpsProxy: Not authorised");
require(
_getTaskCreator() == proxyOwner,
"OpsProxy: Only tasks created by owner"
);
} // else msg.sender == proxyOwner
_;
}
// solhint-disable no-empty-blocks
constructor(address _ops) {
ops = _ops;
}
receive() external payable {}
///@inheritdoc IOpsProxy
function batchExecuteCall(
address[] calldata _targets,
bytes[] calldata _datas,
uint256[] calldata _values
) external payable override onlyAuth {
uint256 length = _targets.length;
require(
length == _datas.length && length == _values.length,
"OpsProxy: Length mismatch"
);
for (uint256 i; i < length; i++)
_executeCall(_targets[i], _datas[i], _values[i]);
}
///@inheritdoc IOpsProxy
function executeCall(
address _target,
bytes calldata _data,
uint256 _value
) external payable override onlyAuth {
_executeCall(_target, _data, _value);
}
function owner() public view returns (address) {
return _proxyAdmin();
}
function _executeCall(
address _target,
bytes calldata _data,
uint256 _value
) private {
(, bytes memory returnData) = _call(
_target,
_data,
_value,
true,
"OpsProxy.executeCall: "
);
emit ExecuteCall(_target, _data, _value, returnData);
}
function _getTaskCreator() private pure returns (address taskCreator) {
assembly {
taskCreator := shr(96, calldataload(sub(calldatasize(), 20)))
}
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;
import {
Initializable
} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {EIP173OpsProxy} from "../vendor/proxy/EIP173/EIP173OpsProxy.sol";
import {Proxied} from "../vendor/proxy/EIP173/Proxied.sol";
import {IOpsProxyFactory} from "../interfaces/IOpsProxyFactory.sol";
// solhint-disable max-states-count
contract OpsProxyFactory is Initializable, Proxied, IOpsProxyFactory {
address public immutable ops;
address public implementation;
mapping(address => bool) public override whitelistedImplementations;
///@dev track proxy of user
mapping(address => address) internal _proxyOf;
///@dev track owner of proxy
mapping(address => address) internal _ownerOf;
modifier onlyOneProxy(address _account) {
require(_proxyOf[_account] == address(0), "OpsProxyFactory: One proxy");
_;
}
modifier notProxy(address _account) {
require(_ownerOf[_account] == address(0), "OpsProxyFactory: No proxy");
_;
}
constructor(address _ops) {
ops = _ops;
}
function initialize(address _implementation) external initializer {
implementation = _implementation;
whitelistedImplementations[_implementation] = true;
}
///@inheritdoc IOpsProxyFactory
function deploy() external override returns (address payable proxy) {
proxy = deployFor(msg.sender);
}
function setImplementation(address _newImplementation)
external
onlyProxyAdmin
{
address oldImplementation = implementation;
require(
oldImplementation != _newImplementation &&
whitelistedImplementations[_newImplementation],
"OpsProxyFactory: Invalid implementation"
);
implementation = _newImplementation;
emit SetImplementation(oldImplementation, _newImplementation);
}
function updateWhitelistedImplementations(
address _implementation,
bool _whitelist
) external onlyProxyAdmin {
whitelistedImplementations[_implementation] = _whitelist;
emit UpdateWhitelistedImplementation(_implementation, _whitelist);
}
///@inheritdoc IOpsProxyFactory
function getProxyOf(address _account)
external
view
override
returns (address, bool)
{
address proxyAddress = _proxyOf[_account];
if (proxyAddress != address(0)) return (proxyAddress, true);
proxyAddress = determineProxyAddress(_account);
return (proxyAddress, false);
}
///@inheritdoc IOpsProxyFactory
function ownerOf(address _proxy) external view override returns (address) {
return _ownerOf[_proxy];
}
///@inheritdoc IOpsProxyFactory
function deployFor(address owner)
public
override
onlyOneProxy(owner)
notProxy(owner)
returns (address payable proxy)
{
proxy = _deploy(bytes32(0), _getBytecode(owner));
_proxyOf[owner] = proxy;
_ownerOf[proxy] = owner;
emit DeployProxy(msg.sender, owner, address(proxy));
}
///@inheritdoc IOpsProxyFactory
function determineProxyAddress(address _account)
public
view
override
returns (address)
{
address proxyAddress = _proxyOf[_account];
if (proxyAddress != address(0)) return proxyAddress;
bytes memory bytecode = _getBytecode(_account);
bytes32 codeHash = keccak256(
abi.encodePacked(
bytes1(0xff),
address(this),
bytes32(0),
keccak256(bytecode)
)
);
return address(uint160(uint256(codeHash)));
}
function _deploy(bytes32 _salt, bytes memory _bytecode)
internal
returns (address payable proxy)
{
assembly {
let endowment := 0
let bytecodeStart := add(_bytecode, 0x20)
let bytecodeLength := mload(_bytecode)
proxy := create2(endowment, bytecodeStart, bytecodeLength, _salt)
}
}
function _getBytecode(address _owner) internal view returns (bytes memory) {
return
abi.encodePacked(
type(EIP173OpsProxy).creationCode,
abi.encode(address(this), implementation, _owner, bytes(""))
);
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment