├── .gitignore ├── .gitmodules ├── .vscode ├── extensions.json └── settings.json ├── Cargo.toml ├── Makefile.toml ├── README.md ├── contracts ├── .gitignore ├── .openzeppelin │ ├── unknown-1442.json │ └── unknown-534353.json ├── README.md ├── compiled-artifacts │ ├── @openzeppelin │ │ └── contracts │ │ │ └── token │ │ │ └── ERC20 │ │ │ └── extensions │ │ │ └── IERC20Metadata.sol │ │ │ └── IERC20Metadata.json │ └── contracts │ │ ├── OfferManager.sol │ │ └── OfferManager.json │ │ ├── OfferManager.test.sol │ │ └── OfferManagerTest.json │ │ ├── OfferManagerBase.sol │ │ └── OfferManagerBase.json │ │ ├── OfferManagerBaseInterface.sol │ │ └── OfferManagerBaseInterface.json │ │ ├── OfferManagerInterface.sol │ │ └── OfferManagerInterface.json │ │ ├── OfferManagerReverse.sol │ │ └── OfferManagerReverse.json │ │ ├── OfferManagerReverseInterface.sol │ │ └── OfferManagerReverseInterface.json │ │ ├── OfferManagerReverseV2.sol │ │ └── OfferManagerReverseV2.json │ │ ├── OfferManagerReverseV2.test.sol │ │ └── OfferManagerReverseV2Test.json │ │ ├── OfferManagerReverseV2Interface.sol │ │ └── OfferManagerReverseV2Interface.json │ │ ├── OfferManagerV2.sol │ │ └── OfferManagerV2.json │ │ ├── OfferManagerV2.test.sol │ │ └── OfferManagerV2Test.json │ │ ├── OfferManagerV2Interface.sol │ │ └── OfferManagerV2Interface.json │ │ ├── SimpleVerifier.sol │ │ └── SimpleVerifier.json │ │ ├── SimpleVerifier.test.sol │ │ └── SimpleVerifierTest.json │ │ ├── Verifier.sol │ │ └── Verifier.json │ │ ├── Verifier.test.sol │ │ └── VerifierTest.json │ │ ├── VerifierInterface.sol │ │ └── VerifierInterface.json │ │ ├── token │ │ └── ERC20.test.sol │ │ │ └── ERC20Test.json │ │ └── utils │ │ ├── MerkleTree.sol │ │ └── MerkleTree.json │ │ ├── MerkleTreeInterface.sol │ │ └── MerkleTreeInterface.json │ │ ├── Poseidon.sol │ │ └── GoldilocksPoseidon.json │ │ └── TokenAllowListInterface.sol │ │ └── TokenAllowListInterface.json ├── contracts │ ├── OfferManager.sol │ ├── OfferManager.test.sol │ ├── OfferManagerBase.sol │ ├── OfferManagerBaseInterface.sol │ ├── OfferManagerInterface.sol │ ├── OfferManagerReverse.sol │ ├── OfferManagerReverseInterface.sol │ ├── OfferManagerReverseV2.sol │ ├── OfferManagerReverseV2.test.sol │ ├── OfferManagerReverseV2Interface.sol │ ├── OfferManagerV2.sol │ ├── OfferManagerV2.test.sol │ ├── OfferManagerV2Interface.sol │ ├── SimpleVerifier.sol │ ├── SimpleVerifier.test.sol │ ├── Verifier.sol │ ├── Verifier.test.sol │ ├── VerifierInterface.sol │ ├── token │ │ └── ERC20.test.sol │ └── utils │ │ ├── MerkleTree.sol │ │ ├── MerkleTreeInterface.sol │ │ ├── Poseidon.sol │ │ ├── Poseidon.test.sol │ │ └── TokenAllowListInterface.sol ├── foundry.toml ├── hardhat.config.ts ├── package-lock.json ├── package.json ├── remappings.txt ├── reports │ └── gas-report ├── scripts │ ├── deploy.ts │ ├── deployPoseidon.ts │ ├── deployV1.ts │ ├── deployV2.ts │ ├── updateAllowList.ts │ ├── upgradeModifiedV2.ts │ └── upgradeV2.ts ├── test │ ├── OfferManager.ts │ ├── OfferManagerReverse.ts │ ├── OfferManagerReverseV2.ts │ ├── OfferManagerV2.ts │ ├── SimpleVerifier.ts │ ├── Verifier.ts │ └── sampleData.ts └── tsconfig.json ├── docs ├── address.json └── concept.md ├── example.env └── src ├── bin ├── offer_manager.rs └── test_poseidon_contract.rs ├── contracts ├── erc20_interface.rs ├── mod.rs ├── offer_manager.rs ├── offer_manager_base.rs ├── offer_manager_reverse.rs └── verifier.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | /*.env 4 | !/example.env 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "contracts/lib/forge-std"] 2 | path = contracts/lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | branch = v1.5.5 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "rust-lang.rust-analyzer", 4 | "jscearcy.rust-doc-viewer", 5 | "streetsidesoftware.code-spell-checker", 6 | "bierner.markdown-mermaid", 7 | "nomicfoundation.hardhat-solidity", 8 | "esbenp.prettier-vscode" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "files.insertFinalNewline": true, 4 | "files.trimFinalNewlines": true, 5 | "files.trimTrailingWhitespace": true, 6 | "cSpell.words": [ 7 | "biguint", 8 | "Bpchar", 9 | "coeff", 10 | "coeffs", 11 | "coord", 12 | "Eddsa", 13 | "eval", 14 | "evals", 15 | "groth", 16 | "hasher", 17 | "ifft", 18 | "inputize", 19 | "interp", 20 | "interpolant", 21 | "intmax", 22 | "jubjub", 23 | "merkle", 24 | "mkdir", 25 | "multipack", 26 | "noncanonical", 27 | "Plonky", 28 | "polyn", 29 | "repr", 30 | "reqwest", 31 | "rollup", 32 | "rustup", 33 | "schnorr", 34 | "serde", 35 | "structopt", 36 | "subsec", 37 | "transactiondetails", 38 | "vals", 39 | "verkle", 40 | "zkdsa" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "intmax-interoperability-plugin" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = "1.0" 8 | bytes = "1.4" 9 | dotenv = "0.15" 10 | ethers = { version = "1.0", features = ["abigen", "ethers-solc", "legacy", "rustls"] } 11 | hex = "0.4" 12 | hex-literal = "0.3" 13 | open-fastrlp = "0.1.4" 14 | plonky2 = { git = "https://github.com/mir-protocol/plonky2", rev = "beefc91", optional = true } 15 | rand = { version = "0.8.5", optional = true } 16 | reqwest = "0.11" 17 | serde = "1.0" 18 | serde_json = "1.0" 19 | tokio = { version = "1.18", features = ["macros"] } 20 | 21 | [lib] 22 | 23 | [features] 24 | default = ["test_poseidon"] 25 | test_poseidon = ["dep:plonky2", "rand"] 26 | -------------------------------------------------------------------------------- /Makefile.toml: -------------------------------------------------------------------------------- 1 | [tasks.copy-env] 2 | script = [ 3 | "cp -n example.env .env", 4 | ] 5 | 6 | [tasks.compile-contracts] 7 | script = [ 8 | "cd contracts", 9 | "forge compile --out artifacts" 10 | ] 11 | 12 | [tasks.deploy-contracts] 13 | script = [ 14 | "if [ ! -e .env ]", 15 | "then echo '.env does not exist'; exit 1", 16 | "fi", 17 | "export $(grep -v '^#' .env | xargs)", 18 | "cd contracts", 19 | "forge create --rpc-url $RPC_URL --private-key $PRIVATE_KEY --legacy contracts/OfferManager.sol:OfferManager", 20 | "forge create --rpc-url $RPC_URL --private-key $PRIVATE_KEY --legacy contracts/OfferManagerReverse.sol:OfferManagerReverse" 21 | ] 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # INTMAX interoperability plugin 2 | 3 | ## Concept 4 | 5 | It is a smart contract written in Solidity to manage offers to exchange assets on INTMAX and other networks. 6 | The contract allows users to register new offers and update the taker of an existing offer. The offer can be activated by transferring the taker's asset to the maker in exchange for payment. The contract also includes events for tracking the registration, activation, and deactivation of offers. The function nextOfferId returns the ID of the next offer to be registered. 7 | 8 | See also [Concept](./docs/concept.md). 9 | 10 | ## How to deploy OfferManager on local network 11 | 12 | Clone this repository. 13 | 14 | ```sh 15 | # use SSH 16 | git clone git@github.com:InternetMaximalism/intmax-interoperability-plugin.git 17 | # or use HTTPS 18 | git clone https://github.com/InternetMaximalism/intmax-interoperability-plugin.git 19 | cd ​​intmax-interoperability-plugin 20 | ``` 21 | 22 | Setup a local node. 23 | 24 | ```sh 25 | git submodule init 26 | git submodule update 27 | cd contracts 28 | npx hardhat node # port 8545 29 | ``` 30 | 31 | Open another terminal and return to this repository root. 32 | Next, setup environment variables. 33 | 34 | ```sh 35 | cp -n example.env .env 36 | ``` 37 | 38 | In the .env file, `PRIVATE_KEY` is required to deploy the contract. 39 | The code below deploys the contract to the network specified by `RPC_URL`, 40 | so the account must have sufficient ETH in advance. 41 | If you use Hardhat node at `http://localhost:8545`, 42 | the private key in `example.env` may be used without modification. 43 | 44 | ```sh 45 | cd contracts 46 | npx hardhat --network localhost run ./scripts/deploy.ts 47 | ``` 48 | 49 | ## How to develop Solidity 50 | 51 | The address of the deployed contract can be found [here](./docs/address.json). 52 | 53 | ### Offer Manager (Pattern 1) 54 | 55 | Two characters appear below - Mike and Tom. 56 | Mike wants to receive tokens on Scroll from Tom instead of sending tokens on INTMAX to Tom. 57 | 58 | #### 1. Mike sends the token to the address `networkIndex` on INTMAX 59 | 60 | ```sh 61 | intmax tx send -a --amount 1 -i 0x00 --receiver-address 62 | ``` 63 | 64 | For example, if you want to make an offer on Scroll Alpha Testnet: 65 | 66 | ```sh 67 | intmax tx send -a 0x98c1fd6f55e2ccee --amount 1 -i 0x00 --receiver-address scroll 68 | ``` 69 | 70 | Currently supported networks are "scroll" (Scroll Alpha Testnet) and "polygon" (Polygon ZKEVM Testnet). 71 | 72 | You must have a sufficient balance yourself to run the above command. You can check your balance with the following command. 73 | 74 | ```sh 75 | intmax account assets 76 | ``` 77 | 78 | #### 2. Mike calculates witness that he sent the transaction 79 | 80 | ```sh 81 | intmax account transaction-proof 82 | ``` 83 | 84 | #### 3. Mike registers a new offer. 85 | 86 | Call the `register()` function of `OfferManager` contract using the witness obtained earlier. 87 | This declares that he will transfer his burned assets to the account that has transferred ETH to him. 88 | 89 | ```solidity 90 | OfferManagerV2Interface offerManager; 91 | uint256 offerId = offerManager.register( 92 | makerIntmax, 93 | makerAssetId, 94 | makerAmount, 95 | taker, 96 | takerIntmax, 97 | takerTokenAddress, 98 | takerAmount, 99 | witness 100 | ); 101 | ``` 102 | 103 | It requires: 104 | 105 | - `makerAmount` must be less than or equal to `MAX_REMITTANCE_AMOUNT` (about 64 bits). 106 | - `takerTokenAddress` must be a valid address. 107 | - `takerIntmax` must not be zero. 108 | - The offer ID must not be already registered. 109 | - The offer must be valid. 110 | - Given witness is valid. 111 | 112 | #### 4. Tom accepts the offer and transfers the ETH to Mike. 113 | 114 | Call the `activate()` function of `OfferManager` contract. 115 | 116 | ```solidity 117 | OfferManagerV2Interface offerManager; 118 | bool success = offerManager.activate{ 119 | value: takerAmount 120 | }(offerId); 121 | require(success, "fail to activate offer"); 122 | ``` 123 | 124 | It requires: 125 | 126 | - The offer must exist. 127 | - The offer must not be already activated. 128 | - Only the taker can activate it. 129 | - The payment must be equal to or greater than the taker's asset amount. 130 | 131 | #### 5. Tom can merge the assets transferred from Mike on INTMAX. 132 | 133 | ```sh 134 | intmax tx merge 135 | ``` 136 | 137 | ### Offer Manager (Pattern 2) 138 | 139 | This time we will make an offer from Tom. 140 | Tom wants to receive a token on INTMAX from Mike instead of sending a token on Scroll to Mike. 141 | 142 | #### 1. Tom locks his ETH and registers the offer. 143 | 144 | Call the `register()` function of `OfferManagerReverse` contract. 145 | This declares that he will transfer the locked assets to the account that has transferred the specified token on INTMAX to him. 146 | 147 | ```solidity 148 | OfferManagerReverseV2Interface offerManagerReverse; 149 | uint256 offerId = offerManagerReverse.register( 150 | takerIntmaxAddress, 151 | takerTokenAddress, 152 | takerAmount, 153 | maker, 154 | makerAssetId, 155 | makerAmount 156 | ); 157 | ``` 158 | 159 | It requires: 160 | 161 | - The offer ID must not be already registered. 162 | - `makerAmount` must be less than or equal to `MAX_REMITTANCE_AMOUNT` (about 64 bits). 163 | 164 | ATTENTION: This offer cannot be cancelled. 165 | 166 | #### 2. Mike transfers the tokens on INTMAX to Tom 167 | 168 | ```sh 169 | intmax tx send --amount 1 -i 0x00 --receiver-address 170 | ``` 171 | 172 | #### 3. Mike calculates witness that he sent the transaction 173 | 174 | ```sh 175 | intmax account transaction-proof 176 | ``` 177 | 178 | The output of this command is "witness". 179 | 180 | #### 4. Mike activates the offer 181 | 182 | Call the `activate()` function of `OfferManagerReverse` contract using the witness obtained earlier. 183 | After that, Mike received Tom's ETH. 184 | 185 | ```solidity 186 | OfferManagerReverseV2Interface offerManagerReverse; 187 | bool success = offerManagerReverse.activate( 188 | offerId, 189 | witness 190 | ); 191 | require(success, "fail to unlock offer"); 192 | ``` 193 | 194 | It requires: 195 | 196 | - The offer must exist. 197 | - The offer must not be already activated. 198 | - Only the maker can activate the offer. 199 | - Given witness is valid. 200 | 201 | ### Examples 202 | 203 | See [sample-auction-app](https://github.com/InternetMaximalism/intmax-rollup-cli/tree/main/packages/sample-auction-app/ethereum). 204 | -------------------------------------------------------------------------------- /contracts/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | 8 | # Hardhat files 9 | cache 10 | artifacts 11 | /cache_hardhat 12 | /out 13 | compiled-artifacts/**/*.dbg.json 14 | -------------------------------------------------------------------------------- /contracts/README.md: -------------------------------------------------------------------------------- 1 | # Offer Manager Contracts for INTMAX Interoperability 2 | 3 | ## How to Test 4 | 5 | ```sh 6 | git submodule update --init 7 | npx hardhat test 8 | REPORT_GAS=true npx hardhat test # if you need to update the gas report 9 | forge compile --sizes 10 | forge test -vv 11 | ``` 12 | 13 | ## Deploy Locally 14 | 15 | ```sh 16 | npx hardhat node 17 | npx hardhat run scripts/deploy.ts --network localhost 18 | ``` 19 | -------------------------------------------------------------------------------- /contracts/compiled-artifacts/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol/IERC20Metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "IERC20Metadata", 4 | "sourceName": "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol", 5 | "abi": [ 6 | { 7 | "anonymous": false, 8 | "inputs": [ 9 | { 10 | "indexed": true, 11 | "internalType": "address", 12 | "name": "owner", 13 | "type": "address" 14 | }, 15 | { 16 | "indexed": true, 17 | "internalType": "address", 18 | "name": "spender", 19 | "type": "address" 20 | }, 21 | { 22 | "indexed": false, 23 | "internalType": "uint256", 24 | "name": "value", 25 | "type": "uint256" 26 | } 27 | ], 28 | "name": "Approval", 29 | "type": "event" 30 | }, 31 | { 32 | "anonymous": false, 33 | "inputs": [ 34 | { 35 | "indexed": true, 36 | "internalType": "address", 37 | "name": "from", 38 | "type": "address" 39 | }, 40 | { 41 | "indexed": true, 42 | "internalType": "address", 43 | "name": "to", 44 | "type": "address" 45 | }, 46 | { 47 | "indexed": false, 48 | "internalType": "uint256", 49 | "name": "value", 50 | "type": "uint256" 51 | } 52 | ], 53 | "name": "Transfer", 54 | "type": "event" 55 | }, 56 | { 57 | "inputs": [ 58 | { 59 | "internalType": "address", 60 | "name": "owner", 61 | "type": "address" 62 | }, 63 | { 64 | "internalType": "address", 65 | "name": "spender", 66 | "type": "address" 67 | } 68 | ], 69 | "name": "allowance", 70 | "outputs": [ 71 | { 72 | "internalType": "uint256", 73 | "name": "", 74 | "type": "uint256" 75 | } 76 | ], 77 | "stateMutability": "view", 78 | "type": "function" 79 | }, 80 | { 81 | "inputs": [ 82 | { 83 | "internalType": "address", 84 | "name": "spender", 85 | "type": "address" 86 | }, 87 | { 88 | "internalType": "uint256", 89 | "name": "amount", 90 | "type": "uint256" 91 | } 92 | ], 93 | "name": "approve", 94 | "outputs": [ 95 | { 96 | "internalType": "bool", 97 | "name": "", 98 | "type": "bool" 99 | } 100 | ], 101 | "stateMutability": "nonpayable", 102 | "type": "function" 103 | }, 104 | { 105 | "inputs": [ 106 | { 107 | "internalType": "address", 108 | "name": "account", 109 | "type": "address" 110 | } 111 | ], 112 | "name": "balanceOf", 113 | "outputs": [ 114 | { 115 | "internalType": "uint256", 116 | "name": "", 117 | "type": "uint256" 118 | } 119 | ], 120 | "stateMutability": "view", 121 | "type": "function" 122 | }, 123 | { 124 | "inputs": [], 125 | "name": "decimals", 126 | "outputs": [ 127 | { 128 | "internalType": "uint8", 129 | "name": "", 130 | "type": "uint8" 131 | } 132 | ], 133 | "stateMutability": "view", 134 | "type": "function" 135 | }, 136 | { 137 | "inputs": [], 138 | "name": "name", 139 | "outputs": [ 140 | { 141 | "internalType": "string", 142 | "name": "", 143 | "type": "string" 144 | } 145 | ], 146 | "stateMutability": "view", 147 | "type": "function" 148 | }, 149 | { 150 | "inputs": [], 151 | "name": "symbol", 152 | "outputs": [ 153 | { 154 | "internalType": "string", 155 | "name": "", 156 | "type": "string" 157 | } 158 | ], 159 | "stateMutability": "view", 160 | "type": "function" 161 | }, 162 | { 163 | "inputs": [], 164 | "name": "totalSupply", 165 | "outputs": [ 166 | { 167 | "internalType": "uint256", 168 | "name": "", 169 | "type": "uint256" 170 | } 171 | ], 172 | "stateMutability": "view", 173 | "type": "function" 174 | }, 175 | { 176 | "inputs": [ 177 | { 178 | "internalType": "address", 179 | "name": "to", 180 | "type": "address" 181 | }, 182 | { 183 | "internalType": "uint256", 184 | "name": "amount", 185 | "type": "uint256" 186 | } 187 | ], 188 | "name": "transfer", 189 | "outputs": [ 190 | { 191 | "internalType": "bool", 192 | "name": "", 193 | "type": "bool" 194 | } 195 | ], 196 | "stateMutability": "nonpayable", 197 | "type": "function" 198 | }, 199 | { 200 | "inputs": [ 201 | { 202 | "internalType": "address", 203 | "name": "from", 204 | "type": "address" 205 | }, 206 | { 207 | "internalType": "address", 208 | "name": "to", 209 | "type": "address" 210 | }, 211 | { 212 | "internalType": "uint256", 213 | "name": "amount", 214 | "type": "uint256" 215 | } 216 | ], 217 | "name": "transferFrom", 218 | "outputs": [ 219 | { 220 | "internalType": "bool", 221 | "name": "", 222 | "type": "bool" 223 | } 224 | ], 225 | "stateMutability": "nonpayable", 226 | "type": "function" 227 | } 228 | ], 229 | "bytecode": "0x", 230 | "deployedBytecode": "0x", 231 | "linkReferences": {}, 232 | "deployedLinkReferences": {} 233 | } 234 | -------------------------------------------------------------------------------- /contracts/compiled-artifacts/contracts/OfferManagerBase.sol/OfferManagerBase.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "OfferManagerBase", 4 | "sourceName": "contracts/OfferManagerBase.sol", 5 | "abi": [ 6 | { 7 | "inputs": [ 8 | { 9 | "internalType": "uint256", 10 | "name": "offerId", 11 | "type": "uint256" 12 | } 13 | ], 14 | "name": "getOffer", 15 | "outputs": [ 16 | { 17 | "internalType": "address", 18 | "name": "maker", 19 | "type": "address" 20 | }, 21 | { 22 | "internalType": "bytes32", 23 | "name": "makerIntmaxAddress", 24 | "type": "bytes32" 25 | }, 26 | { 27 | "internalType": "uint256", 28 | "name": "makerAssetId", 29 | "type": "uint256" 30 | }, 31 | { 32 | "internalType": "uint256", 33 | "name": "makerAmount", 34 | "type": "uint256" 35 | }, 36 | { 37 | "internalType": "address", 38 | "name": "taker", 39 | "type": "address" 40 | }, 41 | { 42 | "internalType": "bytes32", 43 | "name": "takerIntmaxAddress", 44 | "type": "bytes32" 45 | }, 46 | { 47 | "internalType": "address", 48 | "name": "takerTokenAddress", 49 | "type": "address" 50 | }, 51 | { 52 | "internalType": "uint256", 53 | "name": "takerAmount", 54 | "type": "uint256" 55 | }, 56 | { 57 | "internalType": "bool", 58 | "name": "activated", 59 | "type": "bool" 60 | } 61 | ], 62 | "stateMutability": "view", 63 | "type": "function" 64 | }, 65 | { 66 | "inputs": [ 67 | { 68 | "internalType": "uint256", 69 | "name": "offerId", 70 | "type": "uint256" 71 | } 72 | ], 73 | "name": "isActivated", 74 | "outputs": [ 75 | { 76 | "internalType": "bool", 77 | "name": "", 78 | "type": "bool" 79 | } 80 | ], 81 | "stateMutability": "view", 82 | "type": "function" 83 | }, 84 | { 85 | "inputs": [ 86 | { 87 | "internalType": "uint256", 88 | "name": "offerId", 89 | "type": "uint256" 90 | } 91 | ], 92 | "name": "isRegistered", 93 | "outputs": [ 94 | { 95 | "internalType": "bool", 96 | "name": "", 97 | "type": "bool" 98 | } 99 | ], 100 | "stateMutability": "view", 101 | "type": "function" 102 | }, 103 | { 104 | "inputs": [], 105 | "name": "nextOfferId", 106 | "outputs": [ 107 | { 108 | "internalType": "uint256", 109 | "name": "offerId", 110 | "type": "uint256" 111 | } 112 | ], 113 | "stateMutability": "view", 114 | "type": "function" 115 | }, 116 | { 117 | "inputs": [ 118 | { 119 | "internalType": "uint256", 120 | "name": "offerId", 121 | "type": "uint256" 122 | } 123 | ], 124 | "name": "offers", 125 | "outputs": [ 126 | { 127 | "components": [ 128 | { 129 | "internalType": "address", 130 | "name": "maker", 131 | "type": "address" 132 | }, 133 | { 134 | "internalType": "bytes32", 135 | "name": "makerIntmaxAddress", 136 | "type": "bytes32" 137 | }, 138 | { 139 | "internalType": "uint256", 140 | "name": "makerAssetId", 141 | "type": "uint256" 142 | }, 143 | { 144 | "internalType": "uint256", 145 | "name": "makerAmount", 146 | "type": "uint256" 147 | }, 148 | { 149 | "internalType": "address", 150 | "name": "taker", 151 | "type": "address" 152 | }, 153 | { 154 | "internalType": "bytes32", 155 | "name": "takerIntmaxAddress", 156 | "type": "bytes32" 157 | }, 158 | { 159 | "internalType": "address", 160 | "name": "takerTokenAddress", 161 | "type": "address" 162 | }, 163 | { 164 | "internalType": "uint256", 165 | "name": "takerAmount", 166 | "type": "uint256" 167 | }, 168 | { 169 | "internalType": "bool", 170 | "name": "isActivated", 171 | "type": "bool" 172 | } 173 | ], 174 | "internalType": "struct OfferManagerBaseInterface.Offer", 175 | "name": "", 176 | "type": "tuple" 177 | } 178 | ], 179 | "stateMutability": "view", 180 | "type": "function" 181 | } 182 | ], 183 | "bytecode": "0x", 184 | "deployedBytecode": "0x", 185 | "linkReferences": {}, 186 | "deployedLinkReferences": {} 187 | } 188 | -------------------------------------------------------------------------------- /contracts/compiled-artifacts/contracts/OfferManagerBaseInterface.sol/OfferManagerBaseInterface.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "OfferManagerBaseInterface", 4 | "sourceName": "contracts/OfferManagerBaseInterface.sol", 5 | "abi": [ 6 | { 7 | "inputs": [ 8 | { 9 | "internalType": "uint256", 10 | "name": "offerId", 11 | "type": "uint256" 12 | } 13 | ], 14 | "name": "getOffer", 15 | "outputs": [ 16 | { 17 | "internalType": "address", 18 | "name": "maker", 19 | "type": "address" 20 | }, 21 | { 22 | "internalType": "bytes32", 23 | "name": "makerIntmaxAddress", 24 | "type": "bytes32" 25 | }, 26 | { 27 | "internalType": "uint256", 28 | "name": "makerAssetId", 29 | "type": "uint256" 30 | }, 31 | { 32 | "internalType": "uint256", 33 | "name": "makerAmount", 34 | "type": "uint256" 35 | }, 36 | { 37 | "internalType": "address", 38 | "name": "taker", 39 | "type": "address" 40 | }, 41 | { 42 | "internalType": "bytes32", 43 | "name": "takerIntmaxAddress", 44 | "type": "bytes32" 45 | }, 46 | { 47 | "internalType": "address", 48 | "name": "takerTokenAddress", 49 | "type": "address" 50 | }, 51 | { 52 | "internalType": "uint256", 53 | "name": "takerAmount", 54 | "type": "uint256" 55 | }, 56 | { 57 | "internalType": "bool", 58 | "name": "activated", 59 | "type": "bool" 60 | } 61 | ], 62 | "stateMutability": "view", 63 | "type": "function" 64 | }, 65 | { 66 | "inputs": [ 67 | { 68 | "internalType": "uint256", 69 | "name": "offerId", 70 | "type": "uint256" 71 | } 72 | ], 73 | "name": "isActivated", 74 | "outputs": [ 75 | { 76 | "internalType": "bool", 77 | "name": "", 78 | "type": "bool" 79 | } 80 | ], 81 | "stateMutability": "view", 82 | "type": "function" 83 | }, 84 | { 85 | "inputs": [ 86 | { 87 | "internalType": "uint256", 88 | "name": "offerId", 89 | "type": "uint256" 90 | } 91 | ], 92 | "name": "isRegistered", 93 | "outputs": [ 94 | { 95 | "internalType": "bool", 96 | "name": "", 97 | "type": "bool" 98 | } 99 | ], 100 | "stateMutability": "view", 101 | "type": "function" 102 | }, 103 | { 104 | "inputs": [], 105 | "name": "nextOfferId", 106 | "outputs": [ 107 | { 108 | "internalType": "uint256", 109 | "name": "", 110 | "type": "uint256" 111 | } 112 | ], 113 | "stateMutability": "view", 114 | "type": "function" 115 | }, 116 | { 117 | "inputs": [ 118 | { 119 | "internalType": "uint256", 120 | "name": "offerId", 121 | "type": "uint256" 122 | } 123 | ], 124 | "name": "offers", 125 | "outputs": [ 126 | { 127 | "components": [ 128 | { 129 | "internalType": "address", 130 | "name": "maker", 131 | "type": "address" 132 | }, 133 | { 134 | "internalType": "bytes32", 135 | "name": "makerIntmaxAddress", 136 | "type": "bytes32" 137 | }, 138 | { 139 | "internalType": "uint256", 140 | "name": "makerAssetId", 141 | "type": "uint256" 142 | }, 143 | { 144 | "internalType": "uint256", 145 | "name": "makerAmount", 146 | "type": "uint256" 147 | }, 148 | { 149 | "internalType": "address", 150 | "name": "taker", 151 | "type": "address" 152 | }, 153 | { 154 | "internalType": "bytes32", 155 | "name": "takerIntmaxAddress", 156 | "type": "bytes32" 157 | }, 158 | { 159 | "internalType": "address", 160 | "name": "takerTokenAddress", 161 | "type": "address" 162 | }, 163 | { 164 | "internalType": "uint256", 165 | "name": "takerAmount", 166 | "type": "uint256" 167 | }, 168 | { 169 | "internalType": "bool", 170 | "name": "isActivated", 171 | "type": "bool" 172 | } 173 | ], 174 | "internalType": "struct OfferManagerBaseInterface.Offer", 175 | "name": "", 176 | "type": "tuple" 177 | } 178 | ], 179 | "stateMutability": "view", 180 | "type": "function" 181 | } 182 | ], 183 | "bytecode": "0x", 184 | "deployedBytecode": "0x", 185 | "linkReferences": {}, 186 | "deployedLinkReferences": {} 187 | } 188 | -------------------------------------------------------------------------------- /contracts/compiled-artifacts/contracts/OfferManagerInterface.sol/OfferManagerInterface.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "OfferManagerInterface", 4 | "sourceName": "contracts/OfferManagerInterface.sol", 5 | "abi": [ 6 | { 7 | "anonymous": false, 8 | "inputs": [ 9 | { 10 | "indexed": true, 11 | "internalType": "uint256", 12 | "name": "offerId", 13 | "type": "uint256" 14 | }, 15 | { 16 | "indexed": true, 17 | "internalType": "bytes32", 18 | "name": "takerIntmaxAddress", 19 | "type": "bytes32" 20 | } 21 | ], 22 | "name": "OfferActivated", 23 | "type": "event" 24 | }, 25 | { 26 | "anonymous": false, 27 | "inputs": [ 28 | { 29 | "indexed": true, 30 | "internalType": "uint256", 31 | "name": "offerId", 32 | "type": "uint256" 33 | } 34 | ], 35 | "name": "OfferDeactivated", 36 | "type": "event" 37 | }, 38 | { 39 | "anonymous": false, 40 | "inputs": [ 41 | { 42 | "indexed": true, 43 | "internalType": "uint256", 44 | "name": "offerId", 45 | "type": "uint256" 46 | }, 47 | { 48 | "indexed": true, 49 | "internalType": "address", 50 | "name": "maker", 51 | "type": "address" 52 | }, 53 | { 54 | "indexed": false, 55 | "internalType": "bytes32", 56 | "name": "makerIntmaxAddress", 57 | "type": "bytes32" 58 | }, 59 | { 60 | "indexed": false, 61 | "internalType": "uint256", 62 | "name": "makerAssetId", 63 | "type": "uint256" 64 | }, 65 | { 66 | "indexed": false, 67 | "internalType": "uint256", 68 | "name": "makerAmount", 69 | "type": "uint256" 70 | }, 71 | { 72 | "indexed": false, 73 | "internalType": "address", 74 | "name": "taker", 75 | "type": "address" 76 | }, 77 | { 78 | "indexed": false, 79 | "internalType": "address", 80 | "name": "takerTokenAddress", 81 | "type": "address" 82 | }, 83 | { 84 | "indexed": false, 85 | "internalType": "uint256", 86 | "name": "takerAmount", 87 | "type": "uint256" 88 | } 89 | ], 90 | "name": "OfferRegistered", 91 | "type": "event" 92 | }, 93 | { 94 | "anonymous": false, 95 | "inputs": [ 96 | { 97 | "indexed": true, 98 | "internalType": "uint256", 99 | "name": "offerId", 100 | "type": "uint256" 101 | }, 102 | { 103 | "indexed": true, 104 | "internalType": "bytes32", 105 | "name": "takerIntmaxAddress", 106 | "type": "bytes32" 107 | } 108 | ], 109 | "name": "OfferTakerUpdated", 110 | "type": "event" 111 | }, 112 | { 113 | "inputs": [ 114 | { 115 | "internalType": "uint256", 116 | "name": "offerId", 117 | "type": "uint256" 118 | } 119 | ], 120 | "name": "activate", 121 | "outputs": [ 122 | { 123 | "internalType": "bool", 124 | "name": "", 125 | "type": "bool" 126 | } 127 | ], 128 | "stateMutability": "payable", 129 | "type": "function" 130 | }, 131 | { 132 | "inputs": [ 133 | { 134 | "internalType": "uint256", 135 | "name": "offerId", 136 | "type": "uint256" 137 | } 138 | ], 139 | "name": "deactivate", 140 | "outputs": [ 141 | { 142 | "internalType": "bool", 143 | "name": "", 144 | "type": "bool" 145 | } 146 | ], 147 | "stateMutability": "nonpayable", 148 | "type": "function" 149 | }, 150 | { 151 | "inputs": [ 152 | { 153 | "internalType": "uint256", 154 | "name": "offerId", 155 | "type": "uint256" 156 | } 157 | ], 158 | "name": "getOffer", 159 | "outputs": [ 160 | { 161 | "internalType": "address", 162 | "name": "maker", 163 | "type": "address" 164 | }, 165 | { 166 | "internalType": "bytes32", 167 | "name": "makerIntmaxAddress", 168 | "type": "bytes32" 169 | }, 170 | { 171 | "internalType": "uint256", 172 | "name": "makerAssetId", 173 | "type": "uint256" 174 | }, 175 | { 176 | "internalType": "uint256", 177 | "name": "makerAmount", 178 | "type": "uint256" 179 | }, 180 | { 181 | "internalType": "address", 182 | "name": "taker", 183 | "type": "address" 184 | }, 185 | { 186 | "internalType": "bytes32", 187 | "name": "takerIntmaxAddress", 188 | "type": "bytes32" 189 | }, 190 | { 191 | "internalType": "address", 192 | "name": "takerTokenAddress", 193 | "type": "address" 194 | }, 195 | { 196 | "internalType": "uint256", 197 | "name": "takerAmount", 198 | "type": "uint256" 199 | }, 200 | { 201 | "internalType": "bool", 202 | "name": "activated", 203 | "type": "bool" 204 | } 205 | ], 206 | "stateMutability": "view", 207 | "type": "function" 208 | }, 209 | { 210 | "inputs": [ 211 | { 212 | "internalType": "uint256", 213 | "name": "offerId", 214 | "type": "uint256" 215 | } 216 | ], 217 | "name": "isActivated", 218 | "outputs": [ 219 | { 220 | "internalType": "bool", 221 | "name": "", 222 | "type": "bool" 223 | } 224 | ], 225 | "stateMutability": "view", 226 | "type": "function" 227 | }, 228 | { 229 | "inputs": [ 230 | { 231 | "internalType": "uint256", 232 | "name": "offerId", 233 | "type": "uint256" 234 | } 235 | ], 236 | "name": "isRegistered", 237 | "outputs": [ 238 | { 239 | "internalType": "bool", 240 | "name": "", 241 | "type": "bool" 242 | } 243 | ], 244 | "stateMutability": "view", 245 | "type": "function" 246 | }, 247 | { 248 | "inputs": [], 249 | "name": "nextOfferId", 250 | "outputs": [ 251 | { 252 | "internalType": "uint256", 253 | "name": "", 254 | "type": "uint256" 255 | } 256 | ], 257 | "stateMutability": "view", 258 | "type": "function" 259 | }, 260 | { 261 | "inputs": [ 262 | { 263 | "internalType": "uint256", 264 | "name": "offerId", 265 | "type": "uint256" 266 | } 267 | ], 268 | "name": "offers", 269 | "outputs": [ 270 | { 271 | "components": [ 272 | { 273 | "internalType": "address", 274 | "name": "maker", 275 | "type": "address" 276 | }, 277 | { 278 | "internalType": "bytes32", 279 | "name": "makerIntmaxAddress", 280 | "type": "bytes32" 281 | }, 282 | { 283 | "internalType": "uint256", 284 | "name": "makerAssetId", 285 | "type": "uint256" 286 | }, 287 | { 288 | "internalType": "uint256", 289 | "name": "makerAmount", 290 | "type": "uint256" 291 | }, 292 | { 293 | "internalType": "address", 294 | "name": "taker", 295 | "type": "address" 296 | }, 297 | { 298 | "internalType": "bytes32", 299 | "name": "takerIntmaxAddress", 300 | "type": "bytes32" 301 | }, 302 | { 303 | "internalType": "address", 304 | "name": "takerTokenAddress", 305 | "type": "address" 306 | }, 307 | { 308 | "internalType": "uint256", 309 | "name": "takerAmount", 310 | "type": "uint256" 311 | }, 312 | { 313 | "internalType": "bool", 314 | "name": "isActivated", 315 | "type": "bool" 316 | } 317 | ], 318 | "internalType": "struct OfferManagerBaseInterface.Offer", 319 | "name": "", 320 | "type": "tuple" 321 | } 322 | ], 323 | "stateMutability": "view", 324 | "type": "function" 325 | }, 326 | { 327 | "inputs": [ 328 | { 329 | "internalType": "bytes32", 330 | "name": "makerIntmaxAddress", 331 | "type": "bytes32" 332 | }, 333 | { 334 | "internalType": "uint256", 335 | "name": "makerAssetId", 336 | "type": "uint256" 337 | }, 338 | { 339 | "internalType": "uint256", 340 | "name": "makerAmount", 341 | "type": "uint256" 342 | }, 343 | { 344 | "internalType": "address", 345 | "name": "taker", 346 | "type": "address" 347 | }, 348 | { 349 | "internalType": "bytes32", 350 | "name": "takerIntmaxAddress", 351 | "type": "bytes32" 352 | }, 353 | { 354 | "internalType": "address", 355 | "name": "takerTokenAddress", 356 | "type": "address" 357 | }, 358 | { 359 | "internalType": "uint256", 360 | "name": "takerAmount", 361 | "type": "uint256" 362 | } 363 | ], 364 | "name": "register", 365 | "outputs": [ 366 | { 367 | "internalType": "uint256", 368 | "name": "offerId", 369 | "type": "uint256" 370 | } 371 | ], 372 | "stateMutability": "nonpayable", 373 | "type": "function" 374 | }, 375 | { 376 | "inputs": [ 377 | { 378 | "internalType": "uint256", 379 | "name": "offerId", 380 | "type": "uint256" 381 | }, 382 | { 383 | "internalType": "bytes32", 384 | "name": "newTakerIntmaxAddress", 385 | "type": "bytes32" 386 | } 387 | ], 388 | "name": "updateTaker", 389 | "outputs": [], 390 | "stateMutability": "nonpayable", 391 | "type": "function" 392 | } 393 | ], 394 | "bytecode": "0x", 395 | "deployedBytecode": "0x", 396 | "linkReferences": {}, 397 | "deployedLinkReferences": {} 398 | } 399 | -------------------------------------------------------------------------------- /contracts/compiled-artifacts/contracts/OfferManagerReverseInterface.sol/OfferManagerReverseInterface.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "OfferManagerReverseInterface", 4 | "sourceName": "contracts/OfferManagerReverseInterface.sol", 5 | "abi": [ 6 | { 7 | "anonymous": false, 8 | "inputs": [ 9 | { 10 | "indexed": true, 11 | "internalType": "uint256", 12 | "name": "offerId", 13 | "type": "uint256" 14 | }, 15 | { 16 | "indexed": true, 17 | "internalType": "address", 18 | "name": "maker", 19 | "type": "address" 20 | } 21 | ], 22 | "name": "OfferActivated", 23 | "type": "event" 24 | }, 25 | { 26 | "anonymous": false, 27 | "inputs": [ 28 | { 29 | "indexed": true, 30 | "internalType": "uint256", 31 | "name": "offerId", 32 | "type": "uint256" 33 | }, 34 | { 35 | "indexed": true, 36 | "internalType": "address", 37 | "name": "maker", 38 | "type": "address" 39 | } 40 | ], 41 | "name": "OfferMakerUpdated", 42 | "type": "event" 43 | }, 44 | { 45 | "anonymous": false, 46 | "inputs": [ 47 | { 48 | "indexed": true, 49 | "internalType": "uint256", 50 | "name": "offerId", 51 | "type": "uint256" 52 | }, 53 | { 54 | "indexed": true, 55 | "internalType": "address", 56 | "name": "taker", 57 | "type": "address" 58 | }, 59 | { 60 | "indexed": false, 61 | "internalType": "bytes32", 62 | "name": "takerIntmaxAddress", 63 | "type": "bytes32" 64 | }, 65 | { 66 | "indexed": false, 67 | "internalType": "address", 68 | "name": "takerTokenAddress", 69 | "type": "address" 70 | }, 71 | { 72 | "indexed": false, 73 | "internalType": "uint256", 74 | "name": "takerAmount", 75 | "type": "uint256" 76 | }, 77 | { 78 | "indexed": false, 79 | "internalType": "bytes32", 80 | "name": "makerIntmaxAddress", 81 | "type": "bytes32" 82 | }, 83 | { 84 | "indexed": false, 85 | "internalType": "uint256", 86 | "name": "makerAssetId", 87 | "type": "uint256" 88 | }, 89 | { 90 | "indexed": false, 91 | "internalType": "uint256", 92 | "name": "makerAmount", 93 | "type": "uint256" 94 | } 95 | ], 96 | "name": "OfferRegistered", 97 | "type": "event" 98 | }, 99 | { 100 | "inputs": [ 101 | { 102 | "internalType": "uint256", 103 | "name": "offerId", 104 | "type": "uint256" 105 | }, 106 | { 107 | "internalType": "bytes", 108 | "name": "witness", 109 | "type": "bytes" 110 | } 111 | ], 112 | "name": "activate", 113 | "outputs": [ 114 | { 115 | "internalType": "bool", 116 | "name": "", 117 | "type": "bool" 118 | } 119 | ], 120 | "stateMutability": "nonpayable", 121 | "type": "function" 122 | }, 123 | { 124 | "inputs": [ 125 | { 126 | "internalType": "bytes32", 127 | "name": "takerIntmaxAddress", 128 | "type": "bytes32" 129 | }, 130 | { 131 | "internalType": "address", 132 | "name": "takerTokenAddress", 133 | "type": "address" 134 | }, 135 | { 136 | "internalType": "uint256", 137 | "name": "takerAmount", 138 | "type": "uint256" 139 | }, 140 | { 141 | "internalType": "address", 142 | "name": "maker", 143 | "type": "address" 144 | }, 145 | { 146 | "internalType": "uint256", 147 | "name": "makerAssetId", 148 | "type": "uint256" 149 | }, 150 | { 151 | "internalType": "uint256", 152 | "name": "makerAmount", 153 | "type": "uint256" 154 | } 155 | ], 156 | "name": "register", 157 | "outputs": [ 158 | { 159 | "internalType": "uint256", 160 | "name": "offerId", 161 | "type": "uint256" 162 | } 163 | ], 164 | "stateMutability": "payable", 165 | "type": "function" 166 | }, 167 | { 168 | "inputs": [ 169 | { 170 | "internalType": "uint256", 171 | "name": "offerId", 172 | "type": "uint256" 173 | }, 174 | { 175 | "internalType": "address", 176 | "name": "newMaker", 177 | "type": "address" 178 | } 179 | ], 180 | "name": "updateMaker", 181 | "outputs": [], 182 | "stateMutability": "nonpayable", 183 | "type": "function" 184 | } 185 | ], 186 | "bytecode": "0x", 187 | "deployedBytecode": "0x", 188 | "linkReferences": {}, 189 | "deployedLinkReferences": {} 190 | } 191 | -------------------------------------------------------------------------------- /contracts/compiled-artifacts/contracts/OfferManagerReverseV2Interface.sol/OfferManagerReverseV2Interface.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "OfferManagerReverseV2Interface", 4 | "sourceName": "contracts/OfferManagerReverseV2Interface.sol", 5 | "abi": [ 6 | { 7 | "anonymous": false, 8 | "inputs": [ 9 | { 10 | "indexed": true, 11 | "internalType": "uint256", 12 | "name": "offerId", 13 | "type": "uint256" 14 | }, 15 | { 16 | "indexed": true, 17 | "internalType": "address", 18 | "name": "maker", 19 | "type": "address" 20 | } 21 | ], 22 | "name": "OfferActivated", 23 | "type": "event" 24 | }, 25 | { 26 | "anonymous": false, 27 | "inputs": [ 28 | { 29 | "indexed": true, 30 | "internalType": "uint256", 31 | "name": "offerId", 32 | "type": "uint256" 33 | }, 34 | { 35 | "indexed": true, 36 | "internalType": "address", 37 | "name": "maker", 38 | "type": "address" 39 | } 40 | ], 41 | "name": "OfferMakerUpdated", 42 | "type": "event" 43 | }, 44 | { 45 | "anonymous": false, 46 | "inputs": [ 47 | { 48 | "indexed": true, 49 | "internalType": "uint256", 50 | "name": "offerId", 51 | "type": "uint256" 52 | }, 53 | { 54 | "indexed": true, 55 | "internalType": "address", 56 | "name": "taker", 57 | "type": "address" 58 | }, 59 | { 60 | "indexed": false, 61 | "internalType": "bytes32", 62 | "name": "takerIntmaxAddress", 63 | "type": "bytes32" 64 | }, 65 | { 66 | "indexed": false, 67 | "internalType": "address", 68 | "name": "takerTokenAddress", 69 | "type": "address" 70 | }, 71 | { 72 | "indexed": false, 73 | "internalType": "uint256", 74 | "name": "takerAmount", 75 | "type": "uint256" 76 | }, 77 | { 78 | "indexed": false, 79 | "internalType": "bytes32", 80 | "name": "makerIntmaxAddress", 81 | "type": "bytes32" 82 | }, 83 | { 84 | "indexed": false, 85 | "internalType": "uint256", 86 | "name": "makerAssetId", 87 | "type": "uint256" 88 | }, 89 | { 90 | "indexed": false, 91 | "internalType": "uint256", 92 | "name": "makerAmount", 93 | "type": "uint256" 94 | } 95 | ], 96 | "name": "OfferRegistered", 97 | "type": "event" 98 | }, 99 | { 100 | "anonymous": false, 101 | "inputs": [ 102 | { 103 | "indexed": true, 104 | "internalType": "address", 105 | "name": "token", 106 | "type": "address" 107 | }, 108 | { 109 | "indexed": false, 110 | "internalType": "bool", 111 | "name": "isAllowed", 112 | "type": "bool" 113 | } 114 | ], 115 | "name": "TokenAllowListUpdated", 116 | "type": "event" 117 | }, 118 | { 119 | "inputs": [ 120 | { 121 | "internalType": "uint256", 122 | "name": "offerId", 123 | "type": "uint256" 124 | }, 125 | { 126 | "internalType": "bytes", 127 | "name": "witness", 128 | "type": "bytes" 129 | } 130 | ], 131 | "name": "activate", 132 | "outputs": [ 133 | { 134 | "internalType": "bool", 135 | "name": "", 136 | "type": "bool" 137 | } 138 | ], 139 | "stateMutability": "nonpayable", 140 | "type": "function" 141 | }, 142 | { 143 | "inputs": [ 144 | { 145 | "internalType": "address[]", 146 | "name": "tokens", 147 | "type": "address[]" 148 | } 149 | ], 150 | "name": "addTokenAddressToAllowList", 151 | "outputs": [], 152 | "stateMutability": "nonpayable", 153 | "type": "function" 154 | }, 155 | { 156 | "inputs": [ 157 | { 158 | "internalType": "bytes32", 159 | "name": "takerIntmaxAddress", 160 | "type": "bytes32" 161 | }, 162 | { 163 | "internalType": "address", 164 | "name": "takerTokenAddress", 165 | "type": "address" 166 | }, 167 | { 168 | "internalType": "uint256", 169 | "name": "takerAmount", 170 | "type": "uint256" 171 | }, 172 | { 173 | "internalType": "address", 174 | "name": "maker", 175 | "type": "address" 176 | }, 177 | { 178 | "internalType": "uint256", 179 | "name": "makerAssetId", 180 | "type": "uint256" 181 | }, 182 | { 183 | "internalType": "uint256", 184 | "name": "makerAmount", 185 | "type": "uint256" 186 | } 187 | ], 188 | "name": "register", 189 | "outputs": [ 190 | { 191 | "internalType": "uint256", 192 | "name": "offerId", 193 | "type": "uint256" 194 | } 195 | ], 196 | "stateMutability": "payable", 197 | "type": "function" 198 | }, 199 | { 200 | "inputs": [ 201 | { 202 | "internalType": "address[]", 203 | "name": "tokens", 204 | "type": "address[]" 205 | } 206 | ], 207 | "name": "removeTokenAddressFromAllowList", 208 | "outputs": [], 209 | "stateMutability": "nonpayable", 210 | "type": "function" 211 | }, 212 | { 213 | "inputs": [ 214 | { 215 | "internalType": "address", 216 | "name": "token", 217 | "type": "address" 218 | } 219 | ], 220 | "name": "tokenAllowList", 221 | "outputs": [ 222 | { 223 | "internalType": "bool", 224 | "name": "", 225 | "type": "bool" 226 | } 227 | ], 228 | "stateMutability": "view", 229 | "type": "function" 230 | }, 231 | { 232 | "inputs": [ 233 | { 234 | "internalType": "uint256", 235 | "name": "offerId", 236 | "type": "uint256" 237 | }, 238 | { 239 | "internalType": "address", 240 | "name": "newMaker", 241 | "type": "address" 242 | } 243 | ], 244 | "name": "updateMaker", 245 | "outputs": [], 246 | "stateMutability": "nonpayable", 247 | "type": "function" 248 | } 249 | ], 250 | "bytecode": "0x", 251 | "deployedBytecode": "0x", 252 | "linkReferences": {}, 253 | "deployedLinkReferences": {} 254 | } 255 | -------------------------------------------------------------------------------- /contracts/compiled-artifacts/contracts/OfferManagerV2Interface.sol/OfferManagerV2Interface.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "OfferManagerV2Interface", 4 | "sourceName": "contracts/OfferManagerV2Interface.sol", 5 | "abi": [ 6 | { 7 | "anonymous": false, 8 | "inputs": [ 9 | { 10 | "indexed": true, 11 | "internalType": "uint256", 12 | "name": "offerId", 13 | "type": "uint256" 14 | }, 15 | { 16 | "indexed": true, 17 | "internalType": "bytes32", 18 | "name": "takerIntmaxAddress", 19 | "type": "bytes32" 20 | } 21 | ], 22 | "name": "OfferActivated", 23 | "type": "event" 24 | }, 25 | { 26 | "anonymous": false, 27 | "inputs": [ 28 | { 29 | "indexed": true, 30 | "internalType": "uint256", 31 | "name": "offerId", 32 | "type": "uint256" 33 | } 34 | ], 35 | "name": "OfferDeactivated", 36 | "type": "event" 37 | }, 38 | { 39 | "anonymous": false, 40 | "inputs": [ 41 | { 42 | "indexed": true, 43 | "internalType": "uint256", 44 | "name": "offerId", 45 | "type": "uint256" 46 | }, 47 | { 48 | "indexed": true, 49 | "internalType": "address", 50 | "name": "maker", 51 | "type": "address" 52 | }, 53 | { 54 | "indexed": false, 55 | "internalType": "bytes32", 56 | "name": "makerIntmaxAddress", 57 | "type": "bytes32" 58 | }, 59 | { 60 | "indexed": false, 61 | "internalType": "uint256", 62 | "name": "makerAssetId", 63 | "type": "uint256" 64 | }, 65 | { 66 | "indexed": false, 67 | "internalType": "uint256", 68 | "name": "makerAmount", 69 | "type": "uint256" 70 | }, 71 | { 72 | "indexed": false, 73 | "internalType": "address", 74 | "name": "taker", 75 | "type": "address" 76 | }, 77 | { 78 | "indexed": false, 79 | "internalType": "address", 80 | "name": "takerTokenAddress", 81 | "type": "address" 82 | }, 83 | { 84 | "indexed": false, 85 | "internalType": "uint256", 86 | "name": "takerAmount", 87 | "type": "uint256" 88 | } 89 | ], 90 | "name": "OfferRegistered", 91 | "type": "event" 92 | }, 93 | { 94 | "anonymous": false, 95 | "inputs": [ 96 | { 97 | "indexed": true, 98 | "internalType": "uint256", 99 | "name": "offerId", 100 | "type": "uint256" 101 | }, 102 | { 103 | "indexed": true, 104 | "internalType": "bytes32", 105 | "name": "takerIntmaxAddress", 106 | "type": "bytes32" 107 | } 108 | ], 109 | "name": "OfferTakerUpdated", 110 | "type": "event" 111 | }, 112 | { 113 | "anonymous": false, 114 | "inputs": [ 115 | { 116 | "indexed": true, 117 | "internalType": "address", 118 | "name": "token", 119 | "type": "address" 120 | }, 121 | { 122 | "indexed": false, 123 | "internalType": "bool", 124 | "name": "isAllowed", 125 | "type": "bool" 126 | } 127 | ], 128 | "name": "TokenAllowListUpdated", 129 | "type": "event" 130 | }, 131 | { 132 | "inputs": [ 133 | { 134 | "internalType": "uint256", 135 | "name": "offerId", 136 | "type": "uint256" 137 | } 138 | ], 139 | "name": "activate", 140 | "outputs": [ 141 | { 142 | "internalType": "bool", 143 | "name": "", 144 | "type": "bool" 145 | } 146 | ], 147 | "stateMutability": "payable", 148 | "type": "function" 149 | }, 150 | { 151 | "inputs": [ 152 | { 153 | "internalType": "address[]", 154 | "name": "tokens", 155 | "type": "address[]" 156 | } 157 | ], 158 | "name": "addTokenAddressToAllowList", 159 | "outputs": [], 160 | "stateMutability": "nonpayable", 161 | "type": "function" 162 | }, 163 | { 164 | "inputs": [ 165 | { 166 | "internalType": "uint256", 167 | "name": "offerId", 168 | "type": "uint256" 169 | } 170 | ], 171 | "name": "deactivate", 172 | "outputs": [ 173 | { 174 | "internalType": "bool", 175 | "name": "", 176 | "type": "bool" 177 | } 178 | ], 179 | "stateMutability": "nonpayable", 180 | "type": "function" 181 | }, 182 | { 183 | "inputs": [ 184 | { 185 | "internalType": "uint256", 186 | "name": "offerId", 187 | "type": "uint256" 188 | } 189 | ], 190 | "name": "getOffer", 191 | "outputs": [ 192 | { 193 | "internalType": "address", 194 | "name": "maker", 195 | "type": "address" 196 | }, 197 | { 198 | "internalType": "bytes32", 199 | "name": "makerIntmaxAddress", 200 | "type": "bytes32" 201 | }, 202 | { 203 | "internalType": "uint256", 204 | "name": "makerAssetId", 205 | "type": "uint256" 206 | }, 207 | { 208 | "internalType": "uint256", 209 | "name": "makerAmount", 210 | "type": "uint256" 211 | }, 212 | { 213 | "internalType": "address", 214 | "name": "taker", 215 | "type": "address" 216 | }, 217 | { 218 | "internalType": "bytes32", 219 | "name": "takerIntmaxAddress", 220 | "type": "bytes32" 221 | }, 222 | { 223 | "internalType": "address", 224 | "name": "takerTokenAddress", 225 | "type": "address" 226 | }, 227 | { 228 | "internalType": "uint256", 229 | "name": "takerAmount", 230 | "type": "uint256" 231 | }, 232 | { 233 | "internalType": "bool", 234 | "name": "activated", 235 | "type": "bool" 236 | } 237 | ], 238 | "stateMutability": "view", 239 | "type": "function" 240 | }, 241 | { 242 | "inputs": [ 243 | { 244 | "internalType": "uint256", 245 | "name": "offerId", 246 | "type": "uint256" 247 | } 248 | ], 249 | "name": "isActivated", 250 | "outputs": [ 251 | { 252 | "internalType": "bool", 253 | "name": "", 254 | "type": "bool" 255 | } 256 | ], 257 | "stateMutability": "view", 258 | "type": "function" 259 | }, 260 | { 261 | "inputs": [ 262 | { 263 | "internalType": "uint256", 264 | "name": "offerId", 265 | "type": "uint256" 266 | } 267 | ], 268 | "name": "isRegistered", 269 | "outputs": [ 270 | { 271 | "internalType": "bool", 272 | "name": "", 273 | "type": "bool" 274 | } 275 | ], 276 | "stateMutability": "view", 277 | "type": "function" 278 | }, 279 | { 280 | "inputs": [], 281 | "name": "nextOfferId", 282 | "outputs": [ 283 | { 284 | "internalType": "uint256", 285 | "name": "", 286 | "type": "uint256" 287 | } 288 | ], 289 | "stateMutability": "view", 290 | "type": "function" 291 | }, 292 | { 293 | "inputs": [ 294 | { 295 | "internalType": "uint256", 296 | "name": "offerId", 297 | "type": "uint256" 298 | } 299 | ], 300 | "name": "offers", 301 | "outputs": [ 302 | { 303 | "components": [ 304 | { 305 | "internalType": "address", 306 | "name": "maker", 307 | "type": "address" 308 | }, 309 | { 310 | "internalType": "bytes32", 311 | "name": "makerIntmaxAddress", 312 | "type": "bytes32" 313 | }, 314 | { 315 | "internalType": "uint256", 316 | "name": "makerAssetId", 317 | "type": "uint256" 318 | }, 319 | { 320 | "internalType": "uint256", 321 | "name": "makerAmount", 322 | "type": "uint256" 323 | }, 324 | { 325 | "internalType": "address", 326 | "name": "taker", 327 | "type": "address" 328 | }, 329 | { 330 | "internalType": "bytes32", 331 | "name": "takerIntmaxAddress", 332 | "type": "bytes32" 333 | }, 334 | { 335 | "internalType": "address", 336 | "name": "takerTokenAddress", 337 | "type": "address" 338 | }, 339 | { 340 | "internalType": "uint256", 341 | "name": "takerAmount", 342 | "type": "uint256" 343 | }, 344 | { 345 | "internalType": "bool", 346 | "name": "isActivated", 347 | "type": "bool" 348 | } 349 | ], 350 | "internalType": "struct OfferManagerBaseInterface.Offer", 351 | "name": "", 352 | "type": "tuple" 353 | } 354 | ], 355 | "stateMutability": "view", 356 | "type": "function" 357 | }, 358 | { 359 | "inputs": [ 360 | { 361 | "internalType": "bytes32", 362 | "name": "makerIntmaxAddress", 363 | "type": "bytes32" 364 | }, 365 | { 366 | "internalType": "uint256", 367 | "name": "makerAssetId", 368 | "type": "uint256" 369 | }, 370 | { 371 | "internalType": "uint256", 372 | "name": "makerAmount", 373 | "type": "uint256" 374 | }, 375 | { 376 | "internalType": "address", 377 | "name": "taker", 378 | "type": "address" 379 | }, 380 | { 381 | "internalType": "bytes32", 382 | "name": "takerIntmaxAddress", 383 | "type": "bytes32" 384 | }, 385 | { 386 | "internalType": "address", 387 | "name": "takerTokenAddress", 388 | "type": "address" 389 | }, 390 | { 391 | "internalType": "uint256", 392 | "name": "takerAmount", 393 | "type": "uint256" 394 | }, 395 | { 396 | "internalType": "bytes", 397 | "name": "witness", 398 | "type": "bytes" 399 | } 400 | ], 401 | "name": "register", 402 | "outputs": [ 403 | { 404 | "internalType": "uint256", 405 | "name": "offerId", 406 | "type": "uint256" 407 | } 408 | ], 409 | "stateMutability": "nonpayable", 410 | "type": "function" 411 | }, 412 | { 413 | "inputs": [ 414 | { 415 | "internalType": "bytes32", 416 | "name": "makerIntmaxAddress", 417 | "type": "bytes32" 418 | }, 419 | { 420 | "internalType": "uint256", 421 | "name": "makerAssetId", 422 | "type": "uint256" 423 | }, 424 | { 425 | "internalType": "uint256", 426 | "name": "makerAmount", 427 | "type": "uint256" 428 | }, 429 | { 430 | "internalType": "address", 431 | "name": "taker", 432 | "type": "address" 433 | }, 434 | { 435 | "internalType": "bytes32", 436 | "name": "takerIntmaxAddress", 437 | "type": "bytes32" 438 | }, 439 | { 440 | "internalType": "address", 441 | "name": "takerTokenAddress", 442 | "type": "address" 443 | }, 444 | { 445 | "internalType": "uint256", 446 | "name": "takerAmount", 447 | "type": "uint256" 448 | } 449 | ], 450 | "name": "register", 451 | "outputs": [ 452 | { 453 | "internalType": "uint256", 454 | "name": "offerId", 455 | "type": "uint256" 456 | } 457 | ], 458 | "stateMutability": "nonpayable", 459 | "type": "function" 460 | }, 461 | { 462 | "inputs": [ 463 | { 464 | "internalType": "address[]", 465 | "name": "tokens", 466 | "type": "address[]" 467 | } 468 | ], 469 | "name": "removeTokenAddressFromAllowList", 470 | "outputs": [], 471 | "stateMutability": "nonpayable", 472 | "type": "function" 473 | }, 474 | { 475 | "inputs": [ 476 | { 477 | "internalType": "address", 478 | "name": "token", 479 | "type": "address" 480 | } 481 | ], 482 | "name": "tokenAllowList", 483 | "outputs": [ 484 | { 485 | "internalType": "bool", 486 | "name": "", 487 | "type": "bool" 488 | } 489 | ], 490 | "stateMutability": "view", 491 | "type": "function" 492 | }, 493 | { 494 | "inputs": [ 495 | { 496 | "internalType": "uint256", 497 | "name": "offerId", 498 | "type": "uint256" 499 | }, 500 | { 501 | "internalType": "bytes32", 502 | "name": "newTakerIntmaxAddress", 503 | "type": "bytes32" 504 | } 505 | ], 506 | "name": "updateTaker", 507 | "outputs": [], 508 | "stateMutability": "nonpayable", 509 | "type": "function" 510 | } 511 | ], 512 | "bytecode": "0x", 513 | "deployedBytecode": "0x", 514 | "linkReferences": {}, 515 | "deployedLinkReferences": {} 516 | } 517 | -------------------------------------------------------------------------------- /contracts/compiled-artifacts/contracts/VerifierInterface.sol/VerifierInterface.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "VerifierInterface", 4 | "sourceName": "contracts/VerifierInterface.sol", 5 | "abi": [ 6 | { 7 | "inputs": [], 8 | "name": "networkIndex", 9 | "outputs": [ 10 | { 11 | "internalType": "bytes32", 12 | "name": "", 13 | "type": "bytes32" 14 | } 15 | ], 16 | "stateMutability": "view", 17 | "type": "function" 18 | }, 19 | { 20 | "inputs": [ 21 | { 22 | "components": [ 23 | { 24 | "internalType": "bytes32", 25 | "name": "tokenAddress", 26 | "type": "bytes32" 27 | }, 28 | { 29 | "internalType": "uint256", 30 | "name": "tokenId", 31 | "type": "uint256" 32 | }, 33 | { 34 | "internalType": "uint256", 35 | "name": "amount", 36 | "type": "uint256" 37 | } 38 | ], 39 | "internalType": "struct VerifierInterface.Asset[]", 40 | "name": "assets", 41 | "type": "tuple[]" 42 | }, 43 | { 44 | "internalType": "bytes32", 45 | "name": "recipient", 46 | "type": "bytes32" 47 | }, 48 | { 49 | "internalType": "bytes", 50 | "name": "witness", 51 | "type": "bytes" 52 | } 53 | ], 54 | "name": "verifyAssets", 55 | "outputs": [ 56 | { 57 | "internalType": "bool", 58 | "name": "ok", 59 | "type": "bool" 60 | } 61 | ], 62 | "stateMutability": "view", 63 | "type": "function" 64 | } 65 | ], 66 | "bytecode": "0x", 67 | "deployedBytecode": "0x", 68 | "linkReferences": {}, 69 | "deployedLinkReferences": {} 70 | } 71 | -------------------------------------------------------------------------------- /contracts/compiled-artifacts/contracts/utils/MerkleTreeInterface.sol/MerkleTreeInterface.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "MerkleTreeInterface", 4 | "sourceName": "contracts/utils/MerkleTreeInterface.sol", 5 | "abi": [], 6 | "bytecode": "0x", 7 | "deployedBytecode": "0x", 8 | "linkReferences": {}, 9 | "deployedLinkReferences": {} 10 | } 11 | -------------------------------------------------------------------------------- /contracts/compiled-artifacts/contracts/utils/TokenAllowListInterface.sol/TokenAllowListInterface.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "TokenAllowListInterface", 4 | "sourceName": "contracts/utils/TokenAllowListInterface.sol", 5 | "abi": [ 6 | { 7 | "anonymous": false, 8 | "inputs": [ 9 | { 10 | "indexed": true, 11 | "internalType": "address", 12 | "name": "token", 13 | "type": "address" 14 | }, 15 | { 16 | "indexed": false, 17 | "internalType": "bool", 18 | "name": "isAllowed", 19 | "type": "bool" 20 | } 21 | ], 22 | "name": "TokenAllowListUpdated", 23 | "type": "event" 24 | }, 25 | { 26 | "inputs": [ 27 | { 28 | "internalType": "address[]", 29 | "name": "tokens", 30 | "type": "address[]" 31 | } 32 | ], 33 | "name": "addTokenAddressToAllowList", 34 | "outputs": [], 35 | "stateMutability": "nonpayable", 36 | "type": "function" 37 | }, 38 | { 39 | "inputs": [ 40 | { 41 | "internalType": "address[]", 42 | "name": "tokens", 43 | "type": "address[]" 44 | } 45 | ], 46 | "name": "removeTokenAddressFromAllowList", 47 | "outputs": [], 48 | "stateMutability": "nonpayable", 49 | "type": "function" 50 | }, 51 | { 52 | "inputs": [ 53 | { 54 | "internalType": "address", 55 | "name": "token", 56 | "type": "address" 57 | } 58 | ], 59 | "name": "tokenAllowList", 60 | "outputs": [ 61 | { 62 | "internalType": "bool", 63 | "name": "", 64 | "type": "bool" 65 | } 66 | ], 67 | "stateMutability": "view", 68 | "type": "function" 69 | } 70 | ], 71 | "bytecode": "0x", 72 | "deployedBytecode": "0x", 73 | "linkReferences": {}, 74 | "deployedLinkReferences": {} 75 | } 76 | -------------------------------------------------------------------------------- /contracts/contracts/OfferManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.17; 3 | 4 | import "./OfferManagerInterface.sol"; 5 | import "./OfferManagerBase.sol"; 6 | import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; 7 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 8 | 9 | contract OfferManager is 10 | OfferManagerInterface, 11 | OfferManagerBase, 12 | ContextUpgradeable 13 | { 14 | using CountersUpgradeable for CountersUpgradeable.Counter; 15 | 16 | function initialize() public virtual initializer { 17 | __Context_init(); 18 | } 19 | 20 | function register( 21 | bytes32 makerIntmaxAddress, 22 | uint256 makerAssetId, 23 | uint256 makerAmount, 24 | address taker, 25 | bytes32 takerIntmaxAddress, 26 | address takerTokenAddress, 27 | uint256 takerAmount 28 | ) external virtual returns (uint256 offerId) { 29 | // Check if given `takerTokenAddress` is either ETH or ERC20. 30 | if (takerTokenAddress != address(0)) { 31 | uint256 totalSupply = IERC20(takerTokenAddress).totalSupply(); 32 | require( 33 | totalSupply != 0, 34 | "the total supply of ERC20 must not be zero" 35 | ); 36 | } 37 | 38 | return 39 | _register( 40 | _msgSender(), // maker 41 | makerIntmaxAddress, 42 | makerAssetId, 43 | makerAmount, 44 | taker, 45 | takerIntmaxAddress, 46 | takerTokenAddress, 47 | takerAmount 48 | ); 49 | } 50 | 51 | function updateTaker( 52 | uint256 offerId, 53 | bytes32 newTakerIntmaxAddress 54 | ) external { 55 | // The offer must exist. 56 | require( 57 | isRegistered(offerId), 58 | "This offer ID has not been registered." 59 | ); 60 | 61 | // Caller must have the permission to update the offer. 62 | require( 63 | _msgSender() == _offers[offerId].maker, 64 | "offers can be updated by its maker" 65 | ); 66 | 67 | require( 68 | _checkTaker(newTakerIntmaxAddress), 69 | "`newTakerIntmaxAddress` should not be burn address" 70 | ); 71 | 72 | _offers[offerId].takerIntmaxAddress = newTakerIntmaxAddress; 73 | 74 | emit OfferTakerUpdated(offerId, newTakerIntmaxAddress); 75 | } 76 | 77 | function activate(uint256 offerId) external payable virtual returns (bool) { 78 | address taker = _offers[offerId].taker; 79 | if (taker != address(0)) { 80 | require( 81 | _msgSender() == taker, 82 | "offers can be activated by its taker" 83 | ); 84 | } 85 | 86 | Offer memory offer = _offers[offerId]; 87 | 88 | // The taker transfers his asset to maker. 89 | require( 90 | msg.value >= offer.takerAmount, 91 | "please send enough money to activate" 92 | ); 93 | 94 | _activate(offerId); 95 | if (offer.takerTokenAddress == address(0)) { 96 | payable(offer.maker).transfer(msg.value); 97 | } else { 98 | require( 99 | msg.value == 0, 100 | "transmission method other than ETH is specified" 101 | ); 102 | bool success = IERC20(offer.takerTokenAddress).transferFrom( 103 | _msgSender(), 104 | offer.maker, 105 | offer.takerAmount 106 | ); 107 | require(success, "fail to transfer ERC20 token"); 108 | } 109 | 110 | return true; 111 | } 112 | 113 | function deactivate(uint256 offerId) external returns (bool) { 114 | require( 115 | _msgSender() == _offers[offerId].maker, 116 | "only the maker of an offer can deactivate it" 117 | ); 118 | 119 | _deactivate(offerId); 120 | 121 | return true; 122 | } 123 | 124 | function isRegistered(uint256 offerId) public view returns (bool) { 125 | return (_offers[offerId].maker != address(0)); 126 | } 127 | 128 | function isActivated(uint256 offerId) public view returns (bool) { 129 | return _offers[offerId].isActivated; 130 | } 131 | 132 | function _register( 133 | address maker, 134 | bytes32 makerIntmaxAddress, 135 | uint256 makerAssetId, 136 | uint256 makerAmount, 137 | address taker, 138 | bytes32 takerIntmaxAddress, 139 | address takerTokenAddress, 140 | uint256 takerAmount 141 | ) internal returns (uint256 offerId) { 142 | require(maker != address(0), "`maker` must not be zero address."); 143 | offerId = _nextOfferId.current(); 144 | require(!isRegistered(offerId), "This offer ID is already registered."); 145 | 146 | Offer memory offer = Offer({ 147 | maker: maker, 148 | makerIntmaxAddress: makerIntmaxAddress, 149 | makerAssetId: makerAssetId, 150 | makerAmount: makerAmount, 151 | taker: taker, 152 | takerIntmaxAddress: takerIntmaxAddress, 153 | takerTokenAddress: takerTokenAddress, 154 | takerAmount: takerAmount, 155 | isActivated: false 156 | }); 157 | 158 | _isValidOffer(offer); 159 | _offers[offerId] = offer; 160 | _nextOfferId.increment(); 161 | emit OfferRegistered( 162 | offerId, 163 | maker, 164 | makerIntmaxAddress, 165 | makerAssetId, 166 | makerAmount, 167 | taker, 168 | takerTokenAddress, 169 | takerAmount 170 | ); 171 | emit OfferTakerUpdated(offerId, takerIntmaxAddress); 172 | } 173 | 174 | /** 175 | * @dev This function marks the offer as activated. 176 | * @param offerId is the ID of the offer. 177 | */ 178 | function _markOfferAsActivated(uint256 offerId) internal { 179 | require( 180 | isRegistered(offerId), 181 | "This offer ID has not been registered." 182 | ); 183 | require(!isActivated(offerId), "This offer ID is already activated."); 184 | _offers[offerId].isActivated = true; 185 | } 186 | 187 | /** 188 | * @dev This function activates a offer. 189 | * @param offerId is the ID of the offer. 190 | */ 191 | function _activate(uint256 offerId) internal { 192 | _markOfferAsActivated(offerId); 193 | emit OfferActivated(offerId, _offers[offerId].takerIntmaxAddress); 194 | } 195 | 196 | /** 197 | * @dev This function deactivates a offer. 198 | * @param offerId is the ID of the offer. 199 | */ 200 | function _deactivate(uint256 offerId) internal virtual { 201 | _markOfferAsActivated(offerId); 202 | emit OfferDeactivated(offerId); 203 | } 204 | 205 | /** 206 | * @dev Verify the validity of the offer. 207 | * @param offer is the offer that needs to be verified. 208 | */ 209 | function _isValidOffer(Offer memory offer) internal pure virtual { 210 | // The `makerAmount` in the offer must be less than or equal to `MAX_REMITTANCE_AMOUNT`. 211 | require( 212 | offer.makerAmount <= MAX_REMITTANCE_AMOUNT, 213 | "Invalid offer amount: exceeds maximum remittance amount." 214 | ); 215 | // require( 216 | // offer.makerAmount > 0, 217 | // "Maker amount must be greater than zero" 218 | // ); 219 | // require( 220 | // offer.takerAmount > 0, 221 | // "Taker amount must be greater than zero" 222 | // ); 223 | } 224 | 225 | function _checkTaker(bytes32 taker) internal pure virtual returns (bool) { 226 | // A taker should not be the burn address. 227 | return taker != bytes32(0); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /contracts/contracts/OfferManager.test.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.17; 3 | 4 | import "./OfferManager.sol"; 5 | 6 | contract OfferManagerTest is OfferManager { 7 | constructor() { 8 | initialize(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /contracts/contracts/OfferManagerBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.17; 3 | 4 | import "./OfferManagerBaseInterface.sol"; 5 | import "@openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol"; 6 | 7 | abstract contract OfferManagerBase is OfferManagerBaseInterface { 8 | using CountersUpgradeable for CountersUpgradeable.Counter; 9 | 10 | /** 11 | * @dev This constant represents the amount of tokens that can be transferred at one time on INTMAX. 12 | */ 13 | uint256 constant MAX_REMITTANCE_AMOUNT = 18446744069414584320; // the maximum value of Goldilocks field 14 | 15 | /** 16 | * @dev This is the ID allocated to the next offer data to be registered. 17 | */ 18 | CountersUpgradeable.Counter _nextOfferId; 19 | 20 | /** 21 | * @dev This is the mapping from offer ID to offer data. 22 | */ 23 | mapping(uint256 => Offer) _offers; 24 | 25 | function offers(uint256 offerId) external view returns (Offer memory) { 26 | return _offers[offerId]; 27 | } 28 | 29 | function getOffer( 30 | uint256 offerId 31 | ) 32 | public 33 | view 34 | returns ( 35 | address maker, 36 | bytes32 makerIntmaxAddress, 37 | uint256 makerAssetId, 38 | uint256 makerAmount, 39 | address taker, 40 | bytes32 takerIntmaxAddress, 41 | address takerTokenAddress, 42 | uint256 takerAmount, 43 | bool activated 44 | ) 45 | { 46 | Offer storage offer = _offers[offerId]; 47 | maker = offer.maker; 48 | makerIntmaxAddress = offer.makerIntmaxAddress; 49 | makerAssetId = offer.makerAssetId; 50 | makerAmount = offer.makerAmount; 51 | taker = offer.taker; 52 | takerIntmaxAddress = offer.takerIntmaxAddress; 53 | takerTokenAddress = offer.takerTokenAddress; 54 | takerAmount = offer.takerAmount; 55 | activated = offer.isActivated; 56 | } 57 | 58 | function nextOfferId() public view returns (uint256 offerId) { 59 | return _nextOfferId.current(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /contracts/contracts/OfferManagerBaseInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.17; 3 | 4 | interface OfferManagerBaseInterface { 5 | /** 6 | * @notice This struct represents an offer created by a maker and taken by a taker. 7 | * @dev TODO: Placing the isActivated field at the top of the Offer structure can save storage by one slot. 8 | * @param maker is the address of the maker who creates the offer. 9 | * @param makerIntmaxAddress is the INTMAX address of the maker. 10 | * @param makerAssetId is the asset ID that the maker is selling to the taker. 11 | * @param makerAmount is the amount of the asset that the maker is selling to the taker. 12 | * @param taker is the address of the taker who takes the offer. 13 | * @param takerIntmaxAddress is the INTMAX address of the taker. 14 | * @param takerTokenAddress is the address of the token that the taker needs to pay. 15 | * @param takerAmount is the amount of the token that the taker needs to pay. 16 | * @param isActivated is a boolean flag indicating whether the offer is activated or not. 17 | */ 18 | struct Offer { 19 | address maker; 20 | bytes32 makerIntmaxAddress; 21 | uint256 makerAssetId; 22 | uint256 makerAmount; 23 | address taker; 24 | bytes32 takerIntmaxAddress; 25 | address takerTokenAddress; 26 | uint256 takerAmount; 27 | bool isActivated; 28 | } 29 | 30 | function nextOfferId() external view returns (uint256); 31 | 32 | function offers(uint256 offerId) external view returns (Offer memory); 33 | 34 | /** 35 | * DEPRECATED: Please use offers(uint256) instead. 36 | */ 37 | function getOffer( 38 | uint256 offerId 39 | ) 40 | external 41 | view 42 | returns ( 43 | address maker, 44 | bytes32 makerIntmaxAddress, 45 | uint256 makerAssetId, 46 | uint256 makerAmount, 47 | address taker, 48 | bytes32 takerIntmaxAddress, 49 | address takerTokenAddress, 50 | uint256 takerAmount, 51 | bool activated 52 | ); 53 | 54 | function isRegistered(uint256 offerId) external view returns (bool); 55 | 56 | function isActivated(uint256 offerId) external view returns (bool); 57 | } 58 | -------------------------------------------------------------------------------- /contracts/contracts/OfferManagerInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.17; 3 | 4 | import "./OfferManagerBaseInterface.sol"; 5 | 6 | interface OfferManagerInterface is OfferManagerBaseInterface { 7 | /** 8 | * @notice This event occurs when certain offers are registered. 9 | * @param offerId is the ID of the offer. 10 | * @param maker is the maker's account. 11 | * @param makerIntmaxAddress is the maker's INTMAX account. 12 | * @param makerAssetId is the asset ID a maker sell to taker. 13 | * @param makerAmount is the amount a maker sell to taker. 14 | * @param taker is the taker's account. 15 | * @param takerTokenAddress is the token address a taker should pay. 16 | * @param takerAmount is the amount a taker should pay. 17 | */ 18 | event OfferRegistered( 19 | uint256 indexed offerId, 20 | address indexed maker, 21 | bytes32 makerIntmaxAddress, 22 | uint256 makerAssetId, 23 | uint256 makerAmount, 24 | address taker, 25 | address takerTokenAddress, 26 | uint256 takerAmount 27 | ); 28 | 29 | /** 30 | * @notice This event occurs when the taker of an offer is updated. 31 | * @param offerId is the ID of the offer. 32 | * @param takerIntmaxAddress is the taker's INTMAX account. 33 | */ 34 | event OfferTakerUpdated( 35 | uint256 indexed offerId, 36 | bytes32 indexed takerIntmaxAddress 37 | ); 38 | 39 | /** 40 | * @notice This event occurs when certain offers are activated. 41 | * @param offerId is the ID of the offer. 42 | * @param takerIntmaxAddress is the taker's INTMAX address. 43 | */ 44 | event OfferActivated( 45 | uint256 indexed offerId, 46 | bytes32 indexed takerIntmaxAddress 47 | ); 48 | 49 | /** 50 | * @notice This event occurs when an offer is deactivated. 51 | * @param offerId is the ID of the offer. 52 | */ 53 | event OfferDeactivated(uint256 indexed offerId); 54 | 55 | /** 56 | * @notice This function registers a new offer. 57 | * @param makerIntmaxAddress is the maker's INTMAX address. 58 | * @param makerAssetId is the asset ID that the maker is selling to the taker. 59 | * @param makerAmount is the amount of asset that the maker is selling to the taker. 60 | * @param taker is the taker's address. 61 | * @param takerIntmaxAddress is the taker's INTMAX address. 62 | * @param takerTokenAddress is the token address that the taker should pay to the maker. 63 | * @param takerAmount is the amount of token that the taker should pay to the maker. 64 | * @return offerId is the ID of the newly registered offer. 65 | * @dev This function requires: 66 | * - `takerTokenAddress` must be a valid address. 67 | * - `takerIntmax` must not be zero. 68 | * - The caller must not be a zero address. 69 | * - The offer ID must not be already registered. 70 | * - The offer must be valid. 71 | * This function emits: 72 | * - An `OfferRegistered` event with the offer details. 73 | * - An `OfferTakerUpdated` event with the taker's INTMAX address and offer ID. 74 | */ 75 | function register( 76 | bytes32 makerIntmaxAddress, 77 | uint256 makerAssetId, 78 | uint256 makerAmount, 79 | address taker, 80 | bytes32 takerIntmaxAddress, 81 | address takerTokenAddress, 82 | uint256 takerAmount 83 | ) external returns (uint256 offerId); 84 | 85 | /** 86 | * @notice This function updates the taker of an existing offer. 87 | * @param offerId is the ID of the offer to be updated. 88 | * @param newTakerIntmaxAddress is the new taker's INTMAX address. 89 | * @dev This function requires: 90 | * - The offer must exist. 91 | * - The caller must be the maker of the offer. 92 | * - `newTakerIntmaxAddress` must not be zero. 93 | * This function emits: 94 | * - An `OfferTakerUpdated` event with the new taker's INTMAX address and offer ID. 95 | */ 96 | function updateTaker( 97 | uint256 offerId, 98 | bytes32 newTakerIntmaxAddress 99 | ) external; 100 | 101 | /** 102 | * @notice This function activates an offer by transferring the taker's asset to the maker in exchange for payment. 103 | * @param offerId is the ID of the offer to activate. 104 | * @return A boolean indicating whether the offer is successfully activated. 105 | * @dev This function requires: 106 | * - The offer must exist. 107 | * - The offer must not be already activated. 108 | * - Only the taker can activate it if the taker is specified. 109 | * - The payment must be equal to or greater than the taker's asset amount. 110 | * This function emits: 111 | * - An `OfferActivated` event with the offer ID and the taker's INTMAX address. 112 | */ 113 | function activate(uint256 offerId) external payable returns (bool); 114 | 115 | /** 116 | * @notice This function deactivates an offer, preventing it from being activated in the future. 117 | * @param offerId is the ID of the offer to be deactivated. 118 | * @return A boolean indicating whether the deactivation was successful. 119 | * @dev This function is equivalent to the `activate()` function when `takerIntmaxAddress == makerIntmaxAddress`. 120 | * This function requires: 121 | * - The offer must exist. 122 | * - The offer must not be already activated. 123 | * - Only the maker can deactivate it. 124 | * This function emits: 125 | * - An `OfferActivated` event with the offer ID and the maker's INTMAX address. 126 | */ 127 | function deactivate(uint256 offerId) external returns (bool); 128 | } 129 | -------------------------------------------------------------------------------- /contracts/contracts/OfferManagerReverse.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.17; 3 | 4 | import "./OfferManagerReverseInterface.sol"; 5 | import "./OfferManagerBase.sol"; 6 | import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; 7 | import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 8 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 9 | import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 10 | 11 | contract OfferManagerReverse is 12 | OfferManagerReverseInterface, 13 | OfferManagerBase, 14 | ContextUpgradeable, 15 | OwnableUpgradeable 16 | { 17 | using CountersUpgradeable for CountersUpgradeable.Counter; 18 | 19 | function initialize() public initializer { 20 | __Context_init(); 21 | __Ownable_init(); 22 | } 23 | 24 | receive() external payable {} 25 | 26 | function register( 27 | bytes32 takerIntmaxAddress, 28 | address takerTokenAddress, 29 | uint256 takerAmount, 30 | address maker, 31 | uint256 makerAssetId, 32 | uint256 makerAmount 33 | ) external payable virtual returns (uint256 offerId) { 34 | require(_checkMaker(maker), "`maker` must not be zero address."); 35 | 36 | // Check if given `takerTokenAddress` is either ETH or ERC20. 37 | if (takerTokenAddress == address(0)) { 38 | require( 39 | msg.value == takerAmount, 40 | "takerAmount must be the same as msg.value" 41 | ); 42 | } else { 43 | require( 44 | msg.value == 0, 45 | "transmission method other than ETH is specified" 46 | ); 47 | bool success = IERC20(takerTokenAddress).transferFrom( 48 | _msgSender(), 49 | address(this), 50 | takerAmount 51 | ); 52 | require(success, "fail to transfer ERC20 token"); 53 | } 54 | 55 | // require( 56 | // makerIntmaxAddress == bytes32(0), 57 | // "`makerIntmaxAddress` must be zero" 58 | // ); 59 | 60 | return 61 | _register( 62 | _msgSender(), // taker 63 | takerIntmaxAddress, 64 | takerTokenAddress, 65 | msg.value, // takerAmount 66 | maker, 67 | bytes32(0), // anyone activates this offer 68 | makerAssetId, 69 | makerAmount 70 | ); 71 | } 72 | 73 | function updateMaker(uint256 offerId, address newMaker) external { 74 | // The offer must exist. 75 | require( 76 | isRegistered(offerId), 77 | "This offer ID has not been registered." 78 | ); 79 | 80 | // Caller must have the permission to update the offer. 81 | require( 82 | _msgSender() == _offers[offerId].taker, 83 | "Offers can be updated by its taker." 84 | ); 85 | 86 | require(_checkMaker(newMaker), "`newMaker` should not be zero."); 87 | 88 | _offers[offerId].maker = newMaker; 89 | 90 | emit OfferMakerUpdated(offerId, newMaker); 91 | } 92 | 93 | function checkWitness( 94 | uint256 offerId, 95 | bytes calldata witness 96 | ) external view returns (bool) { 97 | _checkWitness(_offers[offerId], witness); 98 | 99 | return true; 100 | } 101 | 102 | function activate( 103 | uint256 offerId, 104 | bytes calldata witness 105 | ) external virtual returns (bool) { 106 | Offer memory offer = _offers[offerId]; 107 | 108 | // address makerIntmaxAddress = _offers[offerId].makerIntmaxAddress; 109 | // if (makerIntmaxAddress != address(0)) { 110 | // require( 111 | // witness.senderIntmax == makerIntmaxAddress, 112 | // "offers can be activated by its taker" 113 | // ); 114 | // } 115 | 116 | _checkWitness(offer, witness); 117 | 118 | require( 119 | _msgSender() == offer.maker, 120 | "Only the maker can unlock this offer." 121 | ); 122 | _markOfferAsActivated(offerId); 123 | 124 | // This contract transfers token to maker. 125 | if (offer.takerTokenAddress == address(0)) { 126 | payable(offer.maker).transfer(offer.takerAmount); 127 | } else { 128 | bool success = IERC20(offer.takerTokenAddress).transfer( 129 | offer.maker, 130 | offer.takerAmount 131 | ); 132 | require(success, "fail to transfer ERC20 token"); 133 | } 134 | 135 | return true; 136 | } 137 | 138 | function isRegistered(uint256 offerId) public view returns (bool) { 139 | return (_offers[offerId].taker != address(0)); 140 | } 141 | 142 | function isActivated(uint256 offerId) public view returns (bool) { 143 | return _offers[offerId].isActivated; 144 | } 145 | 146 | /** 147 | * @dev This function accepts an offer from a maker and registers it with a new offer ID. 148 | * @param taker is the address of the taker. 149 | * @param takerIntmaxAddress is the INTMAX address of the taker. 150 | * @param takerTokenAddress is the address of the token the taker will transfer. 151 | * @param takerAmount is the amount of token the taker will transfer. 152 | * @param maker is the address of the maker. 153 | * @param makerIntmaxAddress is the INTMAX address of the maker. 154 | * @param makerAssetId is the ID of the asset the maker will transfer on INTMAX. 155 | * @param makerAmount is the amount of asset the maker will transfer on INTMAX. 156 | * @return offerId is the ID of the newly registered offer. 157 | */ 158 | function _register( 159 | address taker, 160 | bytes32 takerIntmaxAddress, 161 | address takerTokenAddress, 162 | uint256 takerAmount, 163 | address maker, 164 | bytes32 makerIntmaxAddress, 165 | uint256 makerAssetId, 166 | uint256 makerAmount 167 | ) internal returns (uint256 offerId) { 168 | require(taker != address(0), "The taker must not be zero address."); 169 | offerId = _nextOfferId.current(); 170 | require(!isRegistered(offerId), "Offer ID already registered."); 171 | 172 | Offer memory offer = Offer({ 173 | taker: taker, 174 | takerIntmaxAddress: takerIntmaxAddress, 175 | takerTokenAddress: takerTokenAddress, 176 | takerAmount: takerAmount, 177 | maker: maker, 178 | makerIntmaxAddress: makerIntmaxAddress, 179 | makerAssetId: makerAssetId, 180 | makerAmount: makerAmount, 181 | isActivated: false 182 | }); 183 | 184 | _isValidOffer(offer); 185 | _offers[offerId] = offer; 186 | _nextOfferId.increment(); 187 | emit OfferRegistered( 188 | offerId, 189 | taker, 190 | takerIntmaxAddress, 191 | takerTokenAddress, 192 | takerAmount, 193 | makerIntmaxAddress, 194 | makerAssetId, 195 | makerAmount 196 | ); 197 | emit OfferMakerUpdated(offerId, maker); 198 | } 199 | 200 | /** 201 | * @dev This function marks the offer as activated. 202 | * @param offerId is the ID of the offer. 203 | */ 204 | function _markOfferAsActivated(uint256 offerId) internal { 205 | require( 206 | isRegistered(offerId), 207 | "This offer ID has not been registered." 208 | ); 209 | require(!isActivated(offerId), "This offer ID is already activated."); 210 | _offers[offerId].isActivated = true; 211 | } 212 | 213 | /** 214 | * @dev This function activates a offer and emits an `Unlock` event. 215 | * @param offerId is the ID of the offer to be unlocked. 216 | */ 217 | function _activate(uint256 offerId) internal { 218 | _markOfferAsActivated(offerId); 219 | emit OfferActivated(offerId, _offers[offerId].maker); 220 | } 221 | 222 | /** 223 | * @dev This function checks the validity of the witness signature. 224 | * @param offer is the offer which you would like to verify. 225 | * @param witness is the data that needs to be verified. 226 | */ 227 | function _checkWitness( 228 | Offer memory offer, 229 | bytes memory witness 230 | ) internal view virtual { 231 | // This version does not check anything. 232 | } 233 | 234 | /** 235 | * @dev This function checks the validity of the offer. 236 | * @param offer is the offer that needs to be verified. 237 | */ 238 | function _isValidOffer(Offer memory offer) internal pure { 239 | // The `makerAmount` in the offer must be less than or equal to `MAX_REMITTANCE_AMOUNT`. 240 | require( 241 | offer.makerAmount <= MAX_REMITTANCE_AMOUNT, 242 | "Invalid offer amount: exceeds maximum remittance amount." 243 | ); 244 | // require( 245 | // offer.makerAmount > 0, 246 | // "Maker amount must be greater than zero" 247 | // ); 248 | // require( 249 | // offer.takerAmount > 0, 250 | // "Taker amount must be greater than zero" 251 | // ); 252 | } 253 | 254 | function _checkMaker(address maker) internal pure returns (bool) { 255 | // A maker should not be the zero address. 256 | return maker != address(0); 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /contracts/contracts/OfferManagerReverseInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.17; 3 | 4 | interface OfferManagerReverseInterface { 5 | /** 6 | * @notice This event occurs when certain offers are registered. 7 | * @param offerId is the ID of the offer. 8 | * @param taker is the taker's account. 9 | * @param takerIntmaxAddress is the taker's INTMAX account. 10 | * @param takerTokenAddress is the token address a taker should pay. 11 | * @param takerAmount is the amount a taker should pay. 12 | * @param makerIntmaxAddress is the maker's INTMAX account. 13 | * @param makerAssetId is the asset ID a maker sell to taker. 14 | * @param makerAmount is the amount a maker sell to taker. 15 | */ 16 | event OfferRegistered( 17 | uint256 indexed offerId, 18 | address indexed taker, 19 | bytes32 takerIntmaxAddress, 20 | address takerTokenAddress, 21 | uint256 takerAmount, 22 | bytes32 makerIntmaxAddress, 23 | uint256 makerAssetId, 24 | uint256 makerAmount 25 | ); 26 | 27 | /** 28 | * @notice This event occurs when the maker of an offer is updated. 29 | * @param offerId is the ID of the offer. 30 | * @param maker is the maker's account. 31 | */ 32 | event OfferMakerUpdated(uint256 indexed offerId, address indexed maker); 33 | 34 | /** 35 | * @notice This event occurs when certain offers are activated. 36 | * @param offerId is the ID of the offer. 37 | * @param maker is the maker's account. 38 | */ 39 | event OfferActivated(uint256 indexed offerId, address indexed maker); 40 | 41 | /** 42 | * @notice Locks the taker's funds and creates a new offer to exchange them for the maker's asset on INTMAX. 43 | * ATTENTION: This offer cannot be cancelled. 44 | * @param takerIntmaxAddress is the taker's Intmax address. 45 | * @param takerAmount is the amount of the token that the taker needs to pay. 46 | * @param maker is the address of the maker who will receive the taker's funds. 47 | * @param makerAssetId is the ID of the maker's asset. 48 | * @param makerAmount is the amount of the maker's asset that the taker will receive. 49 | * @return offerId is the ID of the newly created offer. 50 | * @dev This function requires: 51 | * - The caller must not be the zero address. 52 | * - The offer ID must not be already registered. 53 | * - The maker's offer amount must be less than or equal to MAX_REMITTANCE_AMOUNT. 54 | * This function emits: 55 | * - An `OfferRegistered` event with the offer details. 56 | * - An `OfferMakerUpdated` event with the maker's address and offer ID. 57 | */ 58 | function register( 59 | bytes32 takerIntmaxAddress, 60 | address takerTokenAddress, 61 | uint256 takerAmount, 62 | address maker, 63 | uint256 makerAssetId, 64 | uint256 makerAmount 65 | ) external payable returns (uint256 offerId); 66 | 67 | /** 68 | * @notice Updates the maker for the specified offer. 69 | * @param offerId is the ID of the offer to update. 70 | * @param newMaker is a new maker to assign to the offer. 71 | * @dev This function requires: 72 | * - The offer must exist. 73 | * - The caller must be the taker of the offer. 74 | * - `newMaker` must not be zero. 75 | * This function emits: 76 | * - An `OfferMakerUpdated` event with the new maker's address and offer ID. 77 | */ 78 | function updateMaker(uint256 offerId, address newMaker) external; 79 | 80 | /** 81 | * @notice This function accepts an offer and transfers the taker's asset to the maker. 82 | * @param offerId is the ID of the offer. 83 | * @param witness is the witness that maker sends asset to taker on INTMAX. 84 | * @return A boolean indicating whether the offer was successfully unlocked. 85 | * @dev This function requires: 86 | * - The offer must exist. 87 | * - The offer must not be already activated. 88 | * - Only the maker can activate the offer. 89 | * - Given witness is valid. 90 | * This function emits: 91 | * - An `OfferActivated` event with the offer ID and the maker's address. 92 | */ 93 | function activate( 94 | uint256 offerId, 95 | bytes calldata witness 96 | ) external returns (bool); 97 | } 98 | -------------------------------------------------------------------------------- /contracts/contracts/OfferManagerReverseV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.17; 3 | 4 | import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; 5 | import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; 6 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 7 | import "./OfferManagerReverse.sol"; 8 | import "./OfferManagerReverseV2Interface.sol"; 9 | import "./utils/MerkleTree.sol"; 10 | import "./VerifierInterface.sol"; 11 | 12 | contract OfferManagerReverseV2 is 13 | OfferManagerReverseV2Interface, 14 | OfferManagerReverse 15 | { 16 | using SafeERC20Upgradeable for IERC20Upgradeable; 17 | using AddressUpgradeable for address payable; 18 | 19 | VerifierInterface verifier; 20 | mapping(bytes32 => bool) public usedTxHashes; 21 | mapping(address => bool) public tokenAllowList; 22 | 23 | function changeVerifier(VerifierInterface newVerifier) external onlyOwner { 24 | verifier = newVerifier; 25 | } 26 | 27 | function register( 28 | bytes32 takerIntmaxAddress, 29 | address takerTokenAddress, 30 | uint256 takerAmount, 31 | address maker, 32 | uint256 makerAssetId, 33 | uint256 makerAmount 34 | ) 35 | external 36 | payable 37 | override(OfferManagerReverse, OfferManagerReverseInterface) 38 | returns (uint256 offerId) 39 | { 40 | require(_checkMaker(maker), "`maker` must not be zero address."); 41 | 42 | // Check if given `takerTokenAddress` is in the token allow list. 43 | require( 44 | tokenAllowList[takerTokenAddress], 45 | "the taker's token address is not in the token allow list" 46 | ); 47 | 48 | if (takerTokenAddress == address(0)) { 49 | require( 50 | msg.value == takerAmount, 51 | "takerAmount must be the same as msg.value" 52 | ); 53 | } else { 54 | // If it is not ETH, it is deemed to be ERC20. 55 | require( 56 | msg.value == 0, 57 | "transmission method other than ETH is specified" 58 | ); 59 | IERC20Upgradeable(takerTokenAddress).safeTransferFrom( 60 | _msgSender(), 61 | address(this), 62 | takerAmount 63 | ); 64 | } 65 | 66 | return 67 | _register( 68 | _msgSender(), // taker 69 | takerIntmaxAddress, 70 | takerTokenAddress, 71 | takerAmount, // takerAmount 72 | maker, 73 | bytes32(0), // anyone activates this offer 74 | makerAssetId, 75 | makerAmount 76 | ); 77 | } 78 | 79 | function activate( 80 | uint256 offerId, 81 | bytes calldata witness 82 | ) 83 | external 84 | override(OfferManagerReverse, OfferManagerReverseInterface) 85 | returns (bool ok) 86 | { 87 | Offer memory offer = _offers[offerId]; 88 | 89 | // address makerIntmaxAddress = _offers[offerId].makerIntmaxAddress; 90 | // if (makerIntmaxAddress != address(0)) { 91 | // require( 92 | // witness.senderIntmax == makerIntmaxAddress, 93 | // "offers can be activated by its taker" 94 | // ); 95 | // } 96 | 97 | require( 98 | offer.maker == _msgSender(), 99 | "Only the maker can unlock this offer." 100 | ); 101 | 102 | _checkAndNullifyWitness(_offers[offerId], witness); 103 | 104 | _activate(offerId); 105 | 106 | // The maker transfers token to taker. 107 | if (offer.takerTokenAddress == address(0)) { 108 | payable(offer.maker).sendValue(offer.takerAmount); 109 | return true; 110 | } 111 | 112 | IERC20Upgradeable(offer.takerTokenAddress).safeTransfer( 113 | offer.maker, 114 | offer.takerAmount 115 | ); 116 | 117 | return true; 118 | } 119 | 120 | function addTokenAddressToAllowList( 121 | address[] calldata tokens 122 | ) external onlyOwner { 123 | for (uint256 i = 0; i < tokens.length; i++) { 124 | _addTokenAddressToAllowList(tokens[i]); 125 | } 126 | } 127 | 128 | function removeTokenAddressFromAllowList( 129 | address[] calldata tokens 130 | ) external onlyOwner { 131 | for (uint256 i = 0; i < tokens.length; i++) { 132 | _removeTokenAddressFromAllowList(tokens[i]); 133 | } 134 | } 135 | 136 | function _checkAndNullifyWitness( 137 | Offer storage offer, 138 | bytes memory witness 139 | ) internal virtual { 140 | (, , MerkleTree.MerkleProof memory diffTreeInclusionProof, , ) = abi 141 | .decode( 142 | witness, 143 | ( 144 | VerifierInterface.Asset[], 145 | bytes32, 146 | MerkleTreeInterface.MerkleProof, 147 | VerifierInterface.BlockHeader, 148 | bytes 149 | ) 150 | ); 151 | bytes32 txHash = diffTreeInclusionProof.value; 152 | require(!usedTxHashes[txHash], "Given witness already used"); 153 | _checkWitness(offer, witness); 154 | usedTxHashes[txHash] = true; 155 | } 156 | 157 | /** 158 | * @dev Adds `token` to the allow list. 159 | * @param token is the address of token. 160 | */ 161 | function _addTokenAddressToAllowList(address token) internal { 162 | _updateTokenAddressFromAllowList(token, true); 163 | } 164 | 165 | /** 166 | * @dev Removes `token` from the allow list. 167 | * @param token is the address of token. 168 | */ 169 | function _removeTokenAddressFromAllowList(address token) internal { 170 | _updateTokenAddressFromAllowList(token, false); 171 | } 172 | 173 | function _updateTokenAddressFromAllowList( 174 | address token, 175 | bool isAllowed 176 | ) internal { 177 | if (tokenAllowList[token] != isAllowed) { 178 | tokenAllowList[token] = isAllowed; 179 | emit TokenAllowListUpdated(token, isAllowed); 180 | } 181 | } 182 | 183 | /** 184 | * @dev This function checks the validity of the witness signature. 185 | * @param offer is the offer which you would like to verify. 186 | * @param witness is the data that needs to be verified. 187 | */ 188 | function _checkWitness( 189 | Offer memory offer, 190 | bytes memory witness 191 | ) internal view override { 192 | (bytes32 tokenAddress, uint256 tokenId) = _decodeAssetId( 193 | offer.makerAssetId 194 | ); 195 | VerifierInterface.Asset[] memory assets = new VerifierInterface.Asset[]( 196 | 1 197 | ); 198 | assets[0] = VerifierInterface.Asset( 199 | tokenAddress, 200 | tokenId, 201 | offer.makerAmount 202 | ); 203 | bool ok = verifier.verifyAssets( 204 | assets, 205 | offer.takerIntmaxAddress, 206 | witness 207 | ); 208 | require(ok, "Fail to verify assets"); 209 | } 210 | 211 | function _decodeAssetId( 212 | uint256 assetId 213 | ) internal pure returns (bytes32 tokenAddress, uint256 tokenId) { 214 | // (uint64 tokenId, uint192 rawTokenAddress) = abi.decodePacked( 215 | // abi.encode(assetId), 216 | // (uint64, uint192) 217 | // ); 218 | tokenId = (assetId & (type(uint256).max - type(uint192).max)) >> 192; 219 | uint256 rawTokenAddress = assetId & type(uint192).max; 220 | tokenAddress = abi.decode(abi.encode(rawTokenAddress), (bytes32)); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /contracts/contracts/OfferManagerReverseV2.test.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.17; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./SimpleVerifier.test.sol"; 6 | import "./OfferManagerReverseV2.sol"; 7 | 8 | contract OfferManagerReverseV2Test is OfferManagerReverseV2 { 9 | constructor() { 10 | initialize(); 11 | } 12 | } 13 | 14 | contract OfferManagerReverseV2Wrapper is OfferManagerReverseV2 { 15 | constructor() { 16 | initialize(); 17 | } 18 | 19 | function _checkAndNullifyWitness( 20 | Offer storage, 21 | bytes memory 22 | ) internal override {} 23 | } 24 | 25 | contract OfferManagerReverseV2ForgeTest is Test { 26 | address maker; 27 | address taker; 28 | address[] newMakers; 29 | 30 | SimpleVerifier verifier; 31 | OfferManagerReverseV2Wrapper offerManager; 32 | 33 | bytes32 constant NETWORK_INDEX = bytes32("2"); 34 | uint256 constant MAX_REMITTANCE_AMOUNT = 18446744069414584320; // the maximum value of Goldilocks field 35 | 36 | function setUp() external { 37 | string 38 | memory mnemonic = "test test test test test test test test test test test junk"; 39 | { 40 | uint256 privateKey = vm.deriveKey(mnemonic, 0); 41 | maker = vm.addr(privateKey); 42 | } 43 | { 44 | uint256 privateKey = vm.deriveKey(mnemonic, 1); 45 | taker = vm.addr(privateKey); 46 | vm.deal(taker, 100 ether); 47 | } 48 | for (uint32 i = 2; i < 12; i++) { 49 | uint256 privateKey = vm.deriveKey(mnemonic, i); 50 | newMakers.push(vm.addr(privateKey)); 51 | } 52 | 53 | verifier = new SimpleVerifierTest(NETWORK_INDEX); 54 | offerManager = new OfferManagerReverseV2Wrapper(); 55 | offerManager.changeVerifier(VerifierInterface(verifier)); 56 | address[] memory newAllowList = new address[](1); 57 | newAllowList[0] = address(0); 58 | offerManager.addTokenAddressToAllowList(newAllowList); 59 | } 60 | 61 | function testRegisterActivate( 62 | uint256 makerAssetId, 63 | uint256 makerAmount, 64 | bytes32 takerIntmaxAddress, 65 | uint256 takerAmount, 66 | uint256 numMakers 67 | ) external { 68 | address takerTokenAddress = address(0); 69 | bytes memory witness = "0x"; 70 | vm.assume(makerAmount <= MAX_REMITTANCE_AMOUNT); 71 | vm.assume(takerAmount <= 100 ether); 72 | vm.assume(numMakers < newMakers.length); 73 | vm.prank(taker); 74 | uint256 offerId = offerManager.register{value: takerAmount}( 75 | takerIntmaxAddress, 76 | takerTokenAddress, 77 | takerAmount, 78 | maker, 79 | makerAssetId, 80 | makerAmount 81 | ); 82 | 83 | address newMaker = maker; 84 | for (uint256 i = 0; i < numMakers; i++) { 85 | vm.prank(taker); 86 | offerManager.updateMaker(offerId, newMakers[i]); 87 | newMaker = newMakers[i]; 88 | } 89 | 90 | vm.prank(newMaker); 91 | bool ok = offerManager.activate(offerId, witness); 92 | assertEq(ok, true); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /contracts/contracts/OfferManagerReverseV2Interface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.17; 3 | 4 | import "./OfferManagerReverseInterface.sol"; 5 | import "./utils/TokenAllowListInterface.sol"; 6 | 7 | interface OfferManagerReverseV2Interface is 8 | OfferManagerReverseInterface, 9 | TokenAllowListInterface 10 | {} 11 | -------------------------------------------------------------------------------- /contracts/contracts/OfferManagerV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.17; 3 | 4 | import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 5 | import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; 6 | import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; 7 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 8 | import "./OfferManager.sol"; 9 | import "./OfferManagerV2Interface.sol"; 10 | import "./utils/MerkleTree.sol"; 11 | import "./VerifierInterface.sol"; 12 | 13 | contract OfferManagerV2 is 14 | OfferManagerV2Interface, 15 | OfferManager, 16 | OwnableUpgradeable 17 | { 18 | using SafeERC20Upgradeable for IERC20Upgradeable; 19 | using AddressUpgradeable for address payable; 20 | 21 | VerifierInterface verifier; 22 | mapping(bytes32 => bool) public usedTxHashes; 23 | mapping(address => bool) public tokenAllowList; 24 | 25 | function initialize() public override { 26 | OfferManager.initialize(); 27 | initializeV2(_msgSender()); 28 | } 29 | 30 | function initializeV2(address newOwner) public reinitializer(2) { 31 | _transferOwnership(newOwner); 32 | } 33 | 34 | function changeVerifier(VerifierInterface newVerifier) external onlyOwner { 35 | verifier = newVerifier; 36 | } 37 | 38 | /** 39 | * @custom:deprecated This function is deprecated. 40 | */ 41 | function register( 42 | bytes32, 43 | uint256, 44 | uint256, 45 | address, 46 | bytes32, 47 | address, 48 | uint256 49 | ) 50 | external 51 | pure 52 | override(OfferManager, OfferManagerInterface) 53 | returns (uint256) 54 | { 55 | revert("this function is deprecated: 'witness' argument required"); 56 | } 57 | 58 | function register( 59 | bytes32 makerIntmaxAddress, 60 | uint256 makerAssetId, 61 | uint256 makerAmount, 62 | address taker, 63 | bytes32 takerIntmaxAddress, 64 | address takerTokenAddress, 65 | uint256 takerAmount, 66 | bytes memory witness 67 | ) external returns (uint256 offerId) { 68 | // Check if given `takerTokenAddress` is in the token allow list. 69 | require( 70 | tokenAllowList[takerTokenAddress], 71 | "the taker's token address is not in the token allow list" 72 | ); 73 | 74 | offerId = _register( 75 | _msgSender(), // maker 76 | makerIntmaxAddress, 77 | makerAssetId, 78 | makerAmount, 79 | taker, 80 | takerIntmaxAddress, 81 | takerTokenAddress, 82 | takerAmount 83 | ); 84 | 85 | _checkAndNullifyWitness(_offers[offerId], witness); 86 | } 87 | 88 | function activate( 89 | uint256 offerId 90 | ) 91 | external 92 | payable 93 | override(OfferManager, OfferManagerInterface) 94 | returns (bool ok) 95 | { 96 | Offer storage offer = _offers[offerId]; 97 | address taker = offer.taker; 98 | require( 99 | taker == address(0) || taker == _msgSender(), 100 | "offers can be activated by its taker" 101 | ); 102 | 103 | // This part prevents re-entrancy attack (check and effect `offer.isActivated`). 104 | _activate(offerId); 105 | 106 | // The taker transfers his asset to maker. 107 | if (offer.takerTokenAddress == address(0)) { 108 | require( 109 | msg.value == offer.takerAmount, 110 | "please send just the amount needed to activate" 111 | ); 112 | payable(offer.maker).sendValue(msg.value); 113 | return true; 114 | } 115 | 116 | // NOTICE: When the taker transfers ERC20 token to the maker, 117 | // the taker must approve the offer manager to transfer the token. 118 | require(msg.value == 0, "transmission method is not ETH"); 119 | IERC20Upgradeable(offer.takerTokenAddress).safeTransferFrom( 120 | _msgSender(), 121 | offer.maker, 122 | offer.takerAmount 123 | ); 124 | 125 | return true; 126 | } 127 | 128 | function addTokenAddressToAllowList( 129 | address[] calldata tokens 130 | ) external onlyOwner { 131 | for (uint256 i = 0; i < tokens.length; i++) { 132 | _addTokenAddressToAllowList(tokens[i]); 133 | } 134 | } 135 | 136 | function removeTokenAddressFromAllowList( 137 | address[] calldata tokens 138 | ) external onlyOwner { 139 | for (uint256 i = 0; i < tokens.length; i++) { 140 | _removeTokenAddressFromAllowList(tokens[i]); 141 | } 142 | } 143 | 144 | /** 145 | * @custom:deprecated This function is deprecated. 146 | */ 147 | function checkWitness( 148 | uint256 offerId, 149 | bytes calldata witness 150 | ) external view returns (bool) { 151 | _checkWitness(_offers[offerId], witness); 152 | 153 | return true; 154 | } 155 | 156 | function _checkAndNullifyWitness( 157 | Offer storage offer, 158 | bytes memory witness 159 | ) internal virtual { 160 | (, , MerkleTree.MerkleProof memory diffTreeInclusionProof, , ) = abi 161 | .decode( 162 | witness, 163 | ( 164 | VerifierInterface.Asset[], 165 | bytes32, 166 | MerkleTreeInterface.MerkleProof, 167 | VerifierInterface.BlockHeader, 168 | bytes 169 | ) 170 | ); 171 | bytes32 txHash = diffTreeInclusionProof.value; 172 | require(!usedTxHashes[txHash], "Given witness already used"); 173 | _checkWitness(offer, witness); 174 | usedTxHashes[txHash] = true; 175 | } 176 | 177 | /** 178 | * @dev This function checks the validity of the witness signature. 179 | * @param offer is the offer which you would like to verify. 180 | * @param witness is the data that needs to be verified. 181 | */ 182 | function _checkWitness( 183 | Offer memory offer, 184 | bytes memory witness 185 | ) internal view virtual { 186 | bytes32 networkIndex = verifier.networkIndex(); 187 | (bytes32 tokenAddress, uint256 tokenId) = _decodeAssetId( 188 | offer.makerAssetId 189 | ); 190 | VerifierInterface.Asset[] memory assets = new VerifierInterface.Asset[]( 191 | 1 192 | ); 193 | assets[0] = VerifierInterface.Asset( 194 | tokenAddress, 195 | tokenId, 196 | offer.makerAmount 197 | ); 198 | 199 | bool ok = verifier.verifyAssets(assets, networkIndex, witness); 200 | require(ok, "Fail to verify assets"); 201 | } 202 | 203 | function _decodeAssetId( 204 | uint256 assetId 205 | ) internal pure returns (bytes32 tokenAddress, uint256 tokenId) { 206 | // (uint64 tokenId, uint192 rawTokenAddress) = abi.decodePacked( 207 | // abi.encode(assetId), 208 | // (uint64, uint192) 209 | // ); 210 | tokenId = (assetId & (type(uint256).max - type(uint192).max)) >> 192; 211 | uint256 rawTokenAddress = assetId & type(uint192).max; 212 | tokenAddress = abi.decode(abi.encode(rawTokenAddress), (bytes32)); 213 | } 214 | 215 | function _deactivate(uint256 offerId) internal override { 216 | _markOfferAsActivated(offerId); 217 | emit OfferActivated(offerId, _offers[offerId].makerIntmaxAddress); 218 | } 219 | 220 | function _checkTaker( 221 | bytes32 taker 222 | ) internal pure virtual override returns (bool) { 223 | // A taker should not be the burn address. 224 | uint256 takerUint = abi.decode(abi.encode(taker), (uint256)); 225 | return takerUint > 2; 226 | } 227 | 228 | /** 229 | * @dev Adds `token` to the allow list. 230 | * @param token is the address of token. 231 | */ 232 | function _addTokenAddressToAllowList(address token) internal { 233 | _updateTokenAddressFromAllowList(token, true); 234 | } 235 | 236 | /** 237 | * @dev Removes `token` from the allow list. 238 | * @param token is the address of token. 239 | */ 240 | function _removeTokenAddressFromAllowList(address token) internal { 241 | _updateTokenAddressFromAllowList(token, false); 242 | } 243 | 244 | function _updateTokenAddressFromAllowList( 245 | address token, 246 | bool isAllowed 247 | ) internal { 248 | if (tokenAllowList[token] != isAllowed) { 249 | tokenAllowList[token] = isAllowed; 250 | emit TokenAllowListUpdated(token, isAllowed); 251 | } 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /contracts/contracts/OfferManagerV2.test.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.17; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./SimpleVerifier.test.sol"; 6 | import "./OfferManagerV2.sol"; 7 | 8 | contract OfferManagerV2Test is OfferManagerV2 { 9 | constructor() { 10 | initialize(); 11 | } 12 | } 13 | 14 | contract OfferManagerV2Wrapper is OfferManagerV2 { 15 | constructor() { 16 | initialize(); 17 | } 18 | 19 | function checkTaker(bytes32 taker) external pure returns (bool ok) { 20 | return _checkTaker(taker); 21 | } 22 | 23 | function _checkAndNullifyWitness( 24 | Offer storage, 25 | bytes memory 26 | ) internal override {} 27 | } 28 | 29 | contract OfferManagerV2ForgeTest is Test { 30 | address maker; 31 | address taker; 32 | bytes32[] newTakers; 33 | 34 | SimpleVerifier verifier; 35 | OfferManagerV2Wrapper offerManager; 36 | 37 | bytes32 constant NETWORK_INDEX = 38 | 0x0000000000000000000000000000000000000000000000000000000000000002; 39 | uint256 constant MAX_REMITTANCE_AMOUNT = 18446744069414584320; // the maximum value of Goldilocks field 40 | 41 | function setUp() external { 42 | string 43 | memory mnemonic = "test test test test test test test test test test test junk"; 44 | { 45 | uint256 privateKey = vm.deriveKey(mnemonic, 0); 46 | maker = vm.addr(privateKey); 47 | } 48 | { 49 | uint256 privateKey = vm.deriveKey(mnemonic, 1); 50 | taker = vm.addr(privateKey); 51 | vm.deal(taker, 100 ether); 52 | } 53 | for (uint256 i = 2; i < 12; i++) { 54 | bytes32 newTaker = keccak256(abi.encode(mnemonic, i)); 55 | newTakers.push(newTaker); 56 | } 57 | 58 | verifier = new SimpleVerifierTest(NETWORK_INDEX); 59 | offerManager = new OfferManagerV2Wrapper(); 60 | offerManager.changeVerifier(VerifierInterface(verifier)); 61 | address[] memory newAllowList = new address[](1); 62 | newAllowList[0] = address(0); 63 | offerManager.addTokenAddressToAllowList(newAllowList); 64 | } 65 | 66 | function testRegisterActivate( 67 | bytes32 makerIntmaxAddress, 68 | uint256 makerAssetId, 69 | uint256 makerAmount, 70 | bytes32 takerIntmaxAddress, 71 | uint256 takerAmount, 72 | uint256 numTakers 73 | ) external { 74 | address takerTokenAddress = address(0); 75 | bytes memory witness = "0x"; 76 | vm.assume(makerAmount <= MAX_REMITTANCE_AMOUNT); 77 | vm.assume(takerAmount <= 100 ether); 78 | vm.assume(numTakers < newTakers.length); 79 | vm.prank(maker); 80 | uint256 offerId = offerManager.register( 81 | makerIntmaxAddress, 82 | makerAssetId, 83 | makerAmount, 84 | taker, 85 | takerIntmaxAddress, 86 | takerTokenAddress, 87 | takerAmount, 88 | witness 89 | ); 90 | 91 | for (uint256 i = 0; i < numTakers; i++) { 92 | vm.prank(maker); 93 | offerManager.updateTaker(offerId, newTakers[i]); 94 | } 95 | 96 | vm.prank(taker); 97 | bool ok = offerManager.activate{value: takerAmount}(offerId); 98 | assertEq(ok, true); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /contracts/contracts/OfferManagerV2Interface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.17; 3 | 4 | import "./OfferManagerInterface.sol"; 5 | import "./utils/TokenAllowListInterface.sol"; 6 | 7 | interface OfferManagerV2Interface is 8 | OfferManagerInterface, 9 | TokenAllowListInterface 10 | { 11 | /** 12 | * @notice This function registers a new offer. 13 | * @param makerIntmaxAddress is the maker's INTMAX address. 14 | * @param makerAssetId is the asset ID that the maker is selling to the taker. 15 | * @param makerAmount is the amount of asset that the maker is selling to the taker. 16 | * @param taker is the taker's address. 17 | * @param takerIntmaxAddress is the taker's INTMAX address. 18 | * @param takerTokenAddress is the token address that the taker should pay to the maker. 19 | * @param takerAmount is the amount of token that the taker should pay to the maker. 20 | * @param witness is the witness that maker burned his assets. 21 | * @return offerId is the ID of the newly registered offer. 22 | * @dev This function requires: 23 | * - `takerTokenAddress` must be a valid address. 24 | * - `takerIntmax` must not be zero. 25 | * - The caller must not be a zero address. 26 | * - The offer ID must not be already registered. 27 | * - The offer must be valid. 28 | * - Given witness is valid. 29 | * This function emits: 30 | * - An `OfferRegistered` event with the offer details. 31 | * - An `OfferTakerUpdated` event with the taker's INTMAX address and offer ID. 32 | */ 33 | function register( 34 | bytes32 makerIntmaxAddress, 35 | uint256 makerAssetId, 36 | uint256 makerAmount, 37 | address taker, 38 | bytes32 takerIntmaxAddress, 39 | address takerTokenAddress, 40 | uint256 takerAmount, 41 | bytes memory witness 42 | ) external returns (uint256 offerId); 43 | } 44 | -------------------------------------------------------------------------------- /contracts/contracts/SimpleVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.17; 3 | 4 | import "./VerifierInterface.sol"; 5 | import "./utils/MerkleTreeInterface.sol"; 6 | import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 7 | import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 8 | 9 | contract SimpleVerifier is VerifierInterface, OwnableUpgradeable { 10 | /** 11 | * @notice This variable is the network index used to identify the chain. 12 | */ 13 | bytes32 public networkIndex; 14 | 15 | /** 16 | * @notice The function initializes the network index and call the initializer function of OwnableUpgradeable contract. 17 | * @dev This function should be executed at the same time as this contract is deployed. 18 | */ 19 | function initialize(bytes32 networkIndex_) public virtual initializer { 20 | networkIndex = networkIndex_; 21 | __Ownable_init(); 22 | } 23 | 24 | /** 25 | * @inheritdoc VerifierInterface 26 | */ 27 | function verifyAssets( 28 | Asset[] calldata assets, 29 | bytes32 recipient, 30 | bytes calldata witness 31 | ) external view virtual returns (bool ok) { 32 | // Decode given `witness`. 33 | ( 34 | Asset[] memory signedAssets, 35 | bytes32 signedRecipient, 36 | MerkleTreeInterface.MerkleProof memory diffTreeInclusionProof, 37 | BlockHeader memory blockHeader, 38 | bytes memory signature 39 | ) = abi.decode( 40 | witness, 41 | ( 42 | Asset[], 43 | bytes32, 44 | MerkleTreeInterface.MerkleProof, 45 | BlockHeader, 46 | bytes 47 | ) 48 | ); 49 | 50 | // Ensure the `recipient` is the same as the recipient in the `witness`. 51 | require(recipient == signedRecipient, "Not same recipient"); 52 | 53 | // Compare each asset in the `assets` with its corresponding asset in the `witness`. 54 | for (uint256 i = 0; i < assets.length; i++) { 55 | require( 56 | assets[i].tokenAddress == signedAssets[i].tokenAddress, 57 | "Not same asset: tokenAddress" 58 | ); 59 | require( 60 | assets[i].tokenId == signedAssets[i].tokenId, 61 | "Not same asset: tokenId" 62 | ); 63 | require( 64 | assets[i].amount == signedAssets[i].amount, 65 | "Not same asset: amount" 66 | ); 67 | } 68 | 69 | // Create a message from the `assets`, `recipient`, `diffTreeInclusionProof`, and `blockHeader`, and hash it. 70 | bytes memory message = abi.encode( 71 | assets, 72 | recipient, 73 | diffTreeInclusionProof, 74 | blockHeader 75 | ); 76 | bytes32 hashedMessage = ECDSA.toEthSignedMessageHash(message); 77 | 78 | // Recover the address of the signer from the hashed message and signature. 79 | address signer = ECDSA.recover(hashedMessage, signature); 80 | 81 | // Ensure the signer is the owner of the contract. 82 | require(signer == owner(), "Fail to verify aggregator's signature."); 83 | 84 | ok = true; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /contracts/contracts/SimpleVerifier.test.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.17; 3 | 4 | import "./SimpleVerifier.sol"; 5 | 6 | contract SimpleVerifierTest is SimpleVerifier { 7 | constructor(bytes32 networkIndex_) { 8 | initialize(networkIndex_); 9 | } 10 | 11 | function calcWitness( 12 | Asset[] calldata assets, 13 | bytes32 recipient, 14 | MerkleTreeInterface.MerkleProof calldata diffTreeInclusionProof, 15 | BlockHeader calldata blockHeader, 16 | bytes calldata signature 17 | ) external pure returns (bytes memory witness) { 18 | witness = abi.encode( 19 | assets, 20 | recipient, 21 | diffTreeInclusionProof, 22 | blockHeader, 23 | signature 24 | ); 25 | } 26 | 27 | function calcSingingMessage( 28 | Asset[] calldata assets, 29 | bytes32 recipient, 30 | MerkleTreeInterface.MerkleProof calldata diffTreeInclusionProof, 31 | BlockHeader calldata blockHeader 32 | ) external pure returns (bytes memory signature) { 33 | signature = abi.encode( 34 | assets, 35 | recipient, 36 | diffTreeInclusionProof, 37 | blockHeader 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /contracts/contracts/Verifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.17; 3 | 4 | import "./VerifierInterface.sol"; 5 | import "./utils/MerkleTree.sol"; 6 | import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 7 | import "@openzeppelin/contracts/access/Ownable.sol"; 8 | 9 | contract Verifier is VerifierInterface, Ownable, MerkleTree { 10 | /** 11 | * @notice This variable is the network index used to identify the chain. 12 | */ 13 | bytes32 public networkIndex; 14 | /** 15 | * @notice This mapping stores the correspondence from block number to transactions digest. 16 | */ 17 | mapping(uint256 => bytes32) public transactionsDigestHistory; 18 | 19 | constructor(bytes32 networkIndex_) { 20 | networkIndex = networkIndex_; 21 | } 22 | 23 | function _calcLeafHash( 24 | bytes32 key, 25 | bytes32 value 26 | ) internal view returns (bytes32 leafHash) { 27 | uint256[4] memory a_hash_out = poseidonHasher.decodeHashOut(key); 28 | uint256[4] memory b_hash_out = poseidonHasher.decodeHashOut(value); 29 | uint256[] memory state = new uint256[](12); 30 | state[0] = a_hash_out[0]; 31 | state[1] = a_hash_out[1]; 32 | state[2] = a_hash_out[2]; 33 | state[3] = a_hash_out[3]; 34 | state[4] = b_hash_out[0]; 35 | state[5] = b_hash_out[1]; 36 | state[6] = b_hash_out[2]; 37 | state[7] = b_hash_out[3]; 38 | state[8] = 1; 39 | state[9] = 1; 40 | state[11] = 1; 41 | state = poseidonHasher.hashNToMNoPad(state, 4); 42 | uint256[4] memory output; 43 | output[0] = state[0]; 44 | output[1] = state[1]; 45 | output[2] = state[2]; 46 | output[3] = state[3]; 47 | leafHash = poseidonHasher.encodeHashOut(output); 48 | } 49 | 50 | function _calcTransactionHash( 51 | Asset[] memory assets, 52 | bytes32 recipient, 53 | bytes32[] memory recipientMerkleSiblings, 54 | bytes32 nonce 55 | ) internal view returns (bytes32 transactionHash) { 56 | require(assets.length == 1); 57 | 58 | bytes32 tokenIdBytes = abi.decode( 59 | abi.encode(assets[0].tokenId), 60 | (bytes32) 61 | ); 62 | bytes32 amountBytes = abi.decode( 63 | abi.encode(assets[0].amount), 64 | (bytes32) 65 | ); 66 | bytes32 innerInnerAssetRoot = _calcLeafHash(tokenIdBytes, amountBytes); 67 | bytes32 innerAssetRoot = _calcLeafHash( 68 | assets[0].tokenAddress, 69 | innerInnerAssetRoot 70 | ); 71 | bytes32 recipientLeaf = _calcLeafHash(recipient, innerAssetRoot); 72 | uint256 recipientIndex = abi.decode(abi.encode(recipient), (uint256)); 73 | MerkleProof memory recipientMerkleProof = MerkleProof( 74 | recipientIndex, 75 | recipientLeaf, 76 | recipientMerkleSiblings 77 | ); 78 | bytes32 diffRoot = _computeMerkleRootRbo(recipientMerkleProof); 79 | transactionHash = poseidonHasher.twoToOne(diffRoot, nonce); 80 | } 81 | 82 | function _calcBlockHash( 83 | BlockHeader memory blockHeader 84 | ) internal view returns (bytes32 blockHash) { 85 | blockHash = poseidonHasher.twoToOne( 86 | blockHeader.transactionsDigest, 87 | blockHeader.depositDigest 88 | ); 89 | 90 | bytes32 blockNumber = abi.decode( 91 | abi.encode(blockHeader.blockNumber), 92 | (bytes32) 93 | ); 94 | bytes32 a = poseidonHasher.twoToOne( 95 | blockNumber, 96 | blockHeader.latestAccountDigest 97 | ); 98 | bytes32 b = poseidonHasher.twoToOne( 99 | blockHeader.depositDigest, 100 | blockHeader.transactionsDigest 101 | ); 102 | bytes32 c = poseidonHasher.twoToOne(a, b); 103 | bytes32 d = poseidonHasher.twoToOne( 104 | blockHeader.proposedWorldStateDigest, 105 | blockHeader.approvedWorldStateDigest 106 | ); 107 | bytes32 e = poseidonHasher.twoToOne(c, d); 108 | 109 | blockHash = poseidonHasher.twoToOne(blockHeader.blockHeadersDigest, e); 110 | } 111 | 112 | function _verifyAsset( 113 | bytes32 transactionsDigest, 114 | Asset memory asset, 115 | bytes32 recipient, 116 | bytes32 nonce, 117 | bytes32[] memory recipientMerkleSiblings, 118 | MerkleTree.MerkleProof memory diffTreeInclusionProof 119 | ) internal view returns (bool ok) { 120 | Asset[] memory assets = new Asset[](1); 121 | assets[0] = asset; 122 | bytes32 txHash = _calcTransactionHash( 123 | assets, 124 | recipient, 125 | recipientMerkleSiblings, 126 | nonce 127 | ); 128 | require( 129 | txHash == diffTreeInclusionProof.value, 130 | "Fail to verify transaction hash" 131 | ); 132 | bytes32 expectedTransactionsDigest = _computeMerkleRoot( 133 | diffTreeInclusionProof 134 | ); 135 | require( 136 | expectedTransactionsDigest == transactionsDigest, 137 | "Fail to verify transactions digest" 138 | ); 139 | // bytes32 expectedBlockHash = _calcBlockHash(blockHeader); 140 | // require(expectedBlockHash == blockHash, "Fail to verify block hash."); 141 | 142 | ok = true; 143 | } 144 | 145 | function _verifyBlockHash( 146 | bytes32 blockHash, 147 | bytes calldata witness // (r, s, v) 148 | ) internal view { 149 | bytes32 hashedMessage = ECDSA.toEthSignedMessageHash(blockHash); 150 | address signer = ECDSA.recover(hashedMessage, witness); 151 | require(signer == owner(), "Fail to verify aggregator's signature."); 152 | } 153 | 154 | function updateTransactionsDigest( 155 | BlockHeader memory blockHeader, 156 | bytes calldata witness 157 | ) external { 158 | bytes32 blockHash = _calcBlockHash(blockHeader); 159 | _verifyBlockHash(blockHash, witness); 160 | transactionsDigestHistory[blockHeader.blockNumber] = blockHeader 161 | .transactionsDigest; 162 | } 163 | 164 | function verifyAssets( 165 | Asset[] calldata assets, 166 | bytes32 recipient, 167 | bytes calldata witness 168 | ) external view override returns (bool ok) { 169 | ( 170 | bytes32 nonce, 171 | bytes32[] memory recipientMerkleSiblings, 172 | MerkleTree.MerkleProof memory diffTreeInclusionProof, 173 | BlockHeader memory blockHeader 174 | ) = abi.decode( 175 | witness, 176 | ( 177 | bytes32, 178 | bytes32[], 179 | MerkleTreeInterface.MerkleProof, 180 | BlockHeader 181 | ) 182 | ); 183 | 184 | bytes32 transactionsDigest = transactionsDigestHistory[ 185 | blockHeader.blockNumber 186 | ]; 187 | require( 188 | transactionsDigest != bytes32(0), 189 | "Transactions digest was not registered" 190 | ); 191 | require(assets.length == 1, "Only one type of asset is available"); 192 | 193 | ok = _verifyAsset( 194 | transactionsDigest, 195 | assets[0], 196 | recipient, 197 | nonce, 198 | recipientMerkleSiblings, 199 | diffTreeInclusionProof 200 | ); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /contracts/contracts/Verifier.test.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.17; 3 | 4 | import "./Verifier.sol"; 5 | 6 | // import "hardhat/console.sol"; 7 | 8 | contract VerifierTest is Verifier { 9 | constructor(bytes32 networkIndex) Verifier(networkIndex) {} 10 | 11 | function verifyBlockHash( 12 | bytes32 blockHash, 13 | bytes calldata witness // (r, s, v) 14 | ) external view returns (bool ok) { 15 | _verifyBlockHash(blockHash, witness); 16 | 17 | ok = true; 18 | } 19 | 20 | function testVerifyAsset( 21 | Asset calldata asset, 22 | bytes32 recipient, 23 | bytes32 transactionsDigest, 24 | bytes32 nonce, 25 | bytes32[] calldata recipientMerkleSiblings, 26 | MerkleTree.MerkleProof calldata diffTreeInclusionProof 27 | ) external view returns (bool ok) { 28 | ok = _verifyAsset( 29 | transactionsDigest, 30 | asset, 31 | recipient, 32 | nonce, 33 | recipientMerkleSiblings, 34 | diffTreeInclusionProof 35 | ); 36 | } 37 | 38 | function calcWitness( 39 | bytes32 nonce, 40 | bytes32[] calldata recipientMerkleSiblings, 41 | MerkleTree.MerkleProof calldata diffTreeInclusionProof, 42 | BlockHeader calldata blockHeader 43 | ) external pure returns (bytes memory witness) { 44 | witness = abi.encode( 45 | nonce, 46 | recipientMerkleSiblings, 47 | diffTreeInclusionProof, 48 | blockHeader 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /contracts/contracts/VerifierInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.17; 3 | 4 | interface VerifierInterface { 5 | /** 6 | * @notice This struct represents asset. 7 | */ 8 | struct Asset { 9 | bytes32 tokenAddress; 10 | uint256 tokenId; 11 | uint256 amount; 12 | } 13 | 14 | /** 15 | * @notice This struct represents a block header. 16 | */ 17 | struct BlockHeader { 18 | uint256 blockNumber; // little endian 19 | bytes32 prevBlockHash; 20 | bytes32 blockHeadersDigest; // block header tree root 21 | bytes32 transactionsDigest; // state diff tree root 22 | bytes32 depositDigest; // deposit tree root (include scroll root) 23 | bytes32 proposedWorldStateDigest; 24 | bytes32 approvedWorldStateDigest; 25 | bytes32 latestAccountDigest; // latest account tree 26 | } 27 | 28 | /** 29 | * @notice This function returns the network index used to identify the chain. 30 | */ 31 | function networkIndex() external view returns (bytes32); 32 | 33 | /** 34 | * @notice This function verifies the assets of the recipient. 35 | * @param assets is an array of Asset struct that contains tokenAddress, tokenId, and amount of the assets. 36 | * @param recipient is the recipient's INTMAX address. 37 | * @param witness is a witness that contains a set of assets, recipient, diffTreeInclusionProof, blockHeader and owner's signature. 38 | * @return ok indicating whether the execution was successful. 39 | */ 40 | function verifyAssets( 41 | Asset[] calldata assets, 42 | bytes32 recipient, 43 | bytes calldata witness 44 | ) external view returns (bool ok); 45 | } 46 | -------------------------------------------------------------------------------- /contracts/contracts/token/ERC20.test.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.17; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | /** 7 | * @notice This contract is the ERC20 token for testing. 8 | * When the contract is deployed, it mints 10^18 tokens to the deployer. 9 | */ 10 | contract ERC20Test is ERC20 { 11 | constructor() ERC20("ERC20Test", "TEST") { 12 | ERC20._mint(_msgSender(), 1e18); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contracts/contracts/utils/MerkleTree.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import "./MerkleTreeInterface.sol"; 5 | import "./Poseidon.sol"; 6 | 7 | contract MerkleTree is MerkleTreeInterface { 8 | GoldilocksPoseidon public poseidonHasher; 9 | 10 | constructor() { 11 | poseidonHasher = new GoldilocksPoseidon(); 12 | } 13 | 14 | // Compure Merkle root. 15 | function _computeMerkleRoot( 16 | MerkleProof memory proof 17 | ) internal view returns (bytes32) { 18 | bytes32 computedHash = proof.value; 19 | uint256 index = proof.index; 20 | 21 | for (uint256 i = 0; i < proof.siblings.length; i++) { 22 | uint256 branchIndex = index & 1; 23 | index = index >> 1; 24 | 25 | if (branchIndex == 1) { 26 | computedHash = poseidonHasher.twoToOne( 27 | proof.siblings[i], 28 | computedHash 29 | ); 30 | } else { 31 | computedHash = poseidonHasher.twoToOne( 32 | computedHash, 33 | proof.siblings[i] 34 | ); 35 | } 36 | } 37 | 38 | return computedHash; 39 | } 40 | 41 | // Compure Merkle root in the case that the proof index has reverse bit order. 42 | function _computeMerkleRootRbo( 43 | MerkleProof memory proof 44 | ) internal view returns (bytes32) { 45 | bytes32 computedHash = proof.value; 46 | uint256 index = proof.index << (256 - proof.siblings.length); 47 | 48 | for (uint256 i = proof.siblings.length; i != 0; i--) { 49 | uint256 branchIndex = (index >> 255) & 1; 50 | index = index << 1; 51 | 52 | if (branchIndex == 1) { 53 | computedHash = poseidonHasher.twoToOne( 54 | proof.siblings[i - 1], 55 | computedHash 56 | ); 57 | } else { 58 | computedHash = poseidonHasher.twoToOne( 59 | computedHash, 60 | proof.siblings[i - 1] 61 | ); 62 | } 63 | } 64 | 65 | return computedHash; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /contracts/contracts/utils/MerkleTreeInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | interface MerkleTreeInterface { 5 | struct MerkleProof { 6 | uint256 index; 7 | bytes32 value; 8 | bytes32[] siblings; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /contracts/contracts/utils/Poseidon.test.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.17; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./Poseidon.sol"; 6 | 7 | contract PoseidonTest is GoldilocksPoseidon { 8 | function testPoseidon() public view { 9 | uint256[] memory input = new uint256[](1); 10 | input[0] = 1; 11 | uint256 gasBefore = gasleft(); 12 | uint256[] memory output = _hashNToMNoPad(input, 4); 13 | uint256 gasAfter = gasleft(); 14 | console.log("used gas: %d", gasBefore - gasAfter); 15 | assert(output[0] == 15020833855946683413); 16 | assert(output[1] == 2541896837400596712); 17 | assert(output[2] == 5158482081674306993); 18 | assert(output[3] == 15736419290823331982); 19 | } 20 | } 21 | 22 | contract PoseidonForgeTest is Test { 23 | GoldilocksPoseidon poseidon; 24 | 25 | function setUp() external { 26 | poseidon = new GoldilocksPoseidon(); 27 | } 28 | 29 | function testTwoToOne( 30 | bytes32 left, 31 | bytes32 right 32 | ) external view returns (bytes32 output) { 33 | output = poseidon.twoToOne(left, right); 34 | } 35 | 36 | function testHashNToMNoPad( 37 | uint256[] memory input, 38 | uint256 numOutputs 39 | ) external returns (uint256[] memory output) { 40 | // vm.assume(input.length != 0); 41 | vm.assume(input.length < 50); 42 | // vm.assume(numOutputs != 0); 43 | vm.assume(numOutputs < 8); 44 | output = poseidon.hashNToMNoPad(input, numOutputs); 45 | assertEq(output.length, numOutputs); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /contracts/contracts/utils/TokenAllowListInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.17; 3 | 4 | interface TokenAllowListInterface { 5 | /** 6 | * @dev Emitted when `token` is updated to `isAllowed`. 7 | * @param token is the address of token. 8 | */ 9 | event TokenAllowListUpdated(address indexed token, bool isAllowed); 10 | 11 | function tokenAllowList(address token) external view returns (bool); 12 | 13 | function addTokenAddressToAllowList(address[] calldata tokens) external; 14 | 15 | function removeTokenAddressFromAllowList( 16 | address[] calldata tokens 17 | ) external; 18 | } 19 | -------------------------------------------------------------------------------- /contracts/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | ffi = false 3 | fuzz_runs = 256 4 | optimizer = true 5 | optimizer_runs = 1000000 6 | remappings = [ 7 | "solmate/=lib/solmate/src/", 8 | "forge-std/=lib/forge-std/src/" 9 | ] 10 | verbosity = 1 11 | 12 | # Extreme Fuzzing CI Profile :P 13 | [profile.ci] 14 | fuzz_runs = 100_000 15 | verbosity = 4 16 | -------------------------------------------------------------------------------- /contracts/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig } from "hardhat/config"; 2 | import "hardhat-gas-reporter"; 3 | import "@nomicfoundation/hardhat-foundry"; 4 | import "@nomicfoundation/hardhat-toolbox"; 5 | import "@openzeppelin/hardhat-upgrades"; 6 | 7 | require("dotenv").config(); 8 | 9 | const PRIVATE_KEY = process.env.PRIVATE_KEY!; 10 | 11 | const config: HardhatUserConfig = { 12 | solidity: { 13 | version: "0.8.17", 14 | settings: { 15 | optimizer: { 16 | enabled: true, 17 | runs: 200, 18 | }, 19 | }, 20 | }, 21 | defaultNetwork: "hardhat", 22 | networks: { 23 | hardhat: {}, 24 | scrollalpha: { 25 | url: `https://alpha-rpc.scroll.io/l2`, 26 | chainId: 534353, 27 | accounts: PRIVATE_KEY ? [PRIVATE_KEY] : [], 28 | }, 29 | polygonzkevmtest: { 30 | url: `https://rpc.public.zkevm-test.net`, 31 | chainId: 1442, 32 | accounts: PRIVATE_KEY ? [PRIVATE_KEY] : [], 33 | }, 34 | }, 35 | gasReporter: { 36 | currency: "USD", 37 | gasPrice: 50, 38 | outputFile: "./reports/gas-report", 39 | noColors: true, 40 | }, 41 | }; 42 | 43 | export default config; 44 | -------------------------------------------------------------------------------- /contracts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intmax-interoperability-contracts", 3 | "version": "0.1.0", 4 | "scripts": { 5 | "compile": "npx hardhat compile", 6 | "test": "npx hardhat test", 7 | "test:gas-report": "REPORT_GAS=true npx hardhat test", 8 | "deploy:localhost": "npx hardhat --network localhost run ./scripts/deploy.ts", 9 | "upgrade:localhost": "npx hardhat --network localhost run ./scripts/upgrade.ts" 10 | }, 11 | "devDependencies": { 12 | "@ethersproject/providers": "^5.7.2", 13 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.6", 14 | "@nomicfoundation/hardhat-foundry": "^1.0.1", 15 | "@nomicfoundation/hardhat-network-helpers": "^1.0.8", 16 | "@nomicfoundation/hardhat-toolbox": "^2.0.1", 17 | "@nomiclabs/hardhat-ethers": "^2.2.2", 18 | "@nomiclabs/hardhat-etherscan": "^3.1.7", 19 | "@openzeppelin/cli": "^2.8.2", 20 | "@openzeppelin/contracts": "^4.8.2", 21 | "@openzeppelin/contracts-upgradeable": "^4.8.2", 22 | "@openzeppelin/hardhat-upgrades": "^1.22.1", 23 | "@typechain/ethers-v5": "^10.2.0", 24 | "@typechain/hardhat": "^6.1.5", 25 | "@types/chai": "^4.3.4", 26 | "@types/mocha": "^10.0.1", 27 | "chai": "^4.3.7", 28 | "ethers": "^5.7.2", 29 | "hardhat": "^2.12.7", 30 | "hardhat-gas-reporter": "^1.0.9", 31 | "solidity-coverage": "^0.8.2", 32 | "ts-node": "^10.9.1", 33 | "typechain": "^8.1.1", 34 | "typescript": "^5.0.3" 35 | }, 36 | "dependencies": { 37 | "dotenv": "^16.0.3" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /contracts/remappings.txt: -------------------------------------------------------------------------------- 1 | @openzeppelin/=node_modules/@openzeppelin/ 2 | ds-test/=lib/forge-std/lib/ds-test/src/ 3 | eth-gas-reporter/=node_modules/eth-gas-reporter/ 4 | forge-std/=lib/forge-std/src/ 5 | hardhat/=node_modules/hardhat/ 6 | solmate/=lib/solmate/src/ 7 | -------------------------------------------------------------------------------- /contracts/reports/gas-report: -------------------------------------------------------------------------------- 1 | ·-------------------------------------------------------------|---------------------------|-------------|-----------------------------· 2 | | Solc version: 0.8.17 · Optimizer enabled: true · Runs: 200 · Block limit: 30000000 gas │ 3 | ······························································|···························|·············|······························ 4 | | Methods │ 5 | ··························|···································|·············|·············|·············|···············|·············· 6 | | Contract · Method · Min · Max · Avg · # calls · usd (avg) │ 7 | ··························|···································|·············|·············|·············|···············|·············· 8 | | ERC20Test · approve · - · - · 46248 · 3 · - │ 9 | ··························|···································|·············|·············|·············|···············|·············· 10 | | ERC20Test · transfer · - · - · 51446 · 3 · - │ 11 | ··························|···································|·············|·············|·············|···············|·············· 12 | | OfferManager · activate · 72923 · 73336 · 73130 · 4 · - │ 13 | ··························|···································|·············|·············|·············|···············|·············· 14 | | OfferManager · initialize · - · - · 45331 · 1 · - │ 15 | ··························|···································|·············|·············|·············|···············|·············· 16 | | OfferManager · register · 197738 · 210142 · 207661 · 5 · - │ 17 | ··························|···································|·············|·············|·············|···············|·············· 18 | | OfferManager · updateTaker · - · - · 30653 · 2 · - │ 19 | ··························|···································|·············|·············|·············|···············|·············· 20 | | OfferManagerReverse · activate · - · - · 144178 · 2 · - │ 21 | ··························|···································|·············|·············|·············|···············|·············· 22 | | OfferManagerReverse · initialize · - · - · 69446 · 1 · - │ 23 | ··························|···································|·············|·············|·············|···············|·············· 24 | | OfferManagerReverse · register · 182699 · 197576 · 190211 · 6 · - │ 25 | ··························|···································|·············|·············|·············|···············|·············· 26 | | OfferManagerReverseV2 · activate · 129524 · 151503 · 138944 · 7 · - │ 27 | ··························|···································|·············|·············|·············|···············|·············· 28 | | OfferManagerReverseV2 · addTokenAddressToAllowList · 48423 · 55748 · 51445 · 5 · - │ 29 | ··························|···································|·············|·············|·············|···············|·············· 30 | | OfferManagerReverseV2 · changeVerifier · 46149 · 53487 · 51034 · 3 · - │ 31 | ··························|···································|·············|·············|·············|···············|·············· 32 | | OfferManagerReverseV2 · initialize · - · - · 69424 · 1 · - │ 33 | ··························|···································|·············|·············|·············|···············|·············· 34 | | OfferManagerReverseV2 · register · 175353 · 244367 · 209776 · 11 · - │ 35 | ··························|···································|·············|·············|·············|···············|·············· 36 | | OfferManagerReverseV2 · removeTokenAddressFromAllowList · - · - · 26762 · 1 · - │ 37 | ··························|···································|·············|·············|·············|···············|·············· 38 | | OfferManagerV2 · activate · 66017 · 88390 · 77204 · 8 · - │ 39 | ··························|···································|·············|·············|·············|···············|·············· 40 | | OfferManagerV2 · addTokenAddressToAllowList · 48401 · 48629 · 48572 · 4 · - │ 41 | ··························|···································|·············|·············|·············|···············|·············· 42 | | OfferManagerV2 · changeVerifier · - · - · 46171 · 1 · - │ 43 | ··························|···································|·············|·············|·············|···············|·············· 44 | | OfferManagerV2 · initialize · - · - · 70937 · 1 · - │ 45 | ··························|···································|·············|·············|·············|···············|·············· 46 | | OfferManagerV2 · register · 253041 · 274741 · 261148 · 9 · - │ 47 | ··························|···································|·············|·············|·············|···············|·············· 48 | | OfferManagerV2 · removeTokenAddressFromAllowList · - · - · 26772 · 2 · - │ 49 | ··························|···································|·············|·············|·············|···············|·············· 50 | | OfferManagerV2 · updateTaker · - · - · 30720 · 2 · - │ 51 | ··························|···································|·············|·············|·············|···············|·············· 52 | | VerifierTest · updateTransactionsDigest · - · - · 996616 · 1 · - │ 53 | ··························|···································|·············|·············|·············|···············|·············· 54 | | Deployments · · % of limit · │ 55 | ······························································|·············|·············|·············|···············|·············· 56 | | ERC20Test · - · - · 657588 · 2.2 % · - │ 57 | ······························································|·············|·············|·············|···············|·············· 58 | | OfferManager · - · - · 1013174 · 3.4 % · - │ 59 | ······························································|·············|·············|·············|···············|·············· 60 | | OfferManagerReverse · - · - · 1181027 · 3.9 % · - │ 61 | ······························································|·············|·············|·············|···············|·············· 62 | | OfferManagerReverseV2 · - · - · 1953100 · 6.5 % · - │ 63 | ······························································|·············|·············|·············|···············|·············· 64 | | OfferManagerTest · - · - · 1047402 · 3.5 % · - │ 65 | ······························································|·············|·············|·············|···············|·············· 66 | | OfferManagerV2 · - · - · 2049704 · 6.8 % · - │ 67 | ······························································|·············|·············|·············|···············|·············· 68 | | SimpleVerifier · - · - · 1027210 · 3.4 % · - │ 69 | ······························································|·············|·············|·············|···············|·············· 70 | | SimpleVerifierTest · 1342045 · 1342117 · 1342099 · 4.5 % · - │ 71 | ······························································|·············|·············|·············|···············|·············· 72 | | VerifierTest · - · - · 7044921 · 23.5 % · - │ 73 | ·-------------------------------------------------------------|-------------|-------------|-------------|---------------|-------------· -------------------------------------------------------------------------------- /contracts/scripts/deploy.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | 3 | const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; 4 | 5 | // Deploy contracts for testing. 6 | async function main() { 7 | const OfferManager = await ethers.getContractFactory("OfferManagerV2Test"); 8 | const offerManager = await OfferManager.deploy(); 9 | await offerManager.deployed(); 10 | 11 | console.log(`Deploy a OfferManager contract: ${offerManager.address}`); 12 | 13 | const OfferManagerReverse = await ethers.getContractFactory( 14 | "OfferManagerReverseV2Test" 15 | ); 16 | const offerManagerReverse = await OfferManagerReverse.deploy(); 17 | await offerManagerReverse.deployed(); 18 | 19 | console.log( 20 | `Deploy a OfferManagerReverse contract: ${offerManagerReverse.address}` 21 | ); 22 | 23 | { 24 | const owner = await offerManager.owner(); 25 | console.log("owner:", owner); 26 | } 27 | { 28 | const owner = await offerManagerReverse.owner(); 29 | console.log("owner:", owner); 30 | } 31 | 32 | const networkIndex = 33 | "0x0000000000000000000000000000000000000000000000000000000000000002"; 34 | const Verifier = await ethers.getContractFactory("SimpleVerifierTest"); 35 | const verifier = await Verifier.deploy(networkIndex); 36 | 37 | console.log(`Deploy a Verifier contract: ${verifier.address}`); 38 | 39 | await offerManager.changeVerifier(verifier.address); 40 | await offerManagerReverse.changeVerifier(verifier.address); 41 | await offerManager.addTokenAddressToAllowList([ZERO_ADDRESS]); 42 | await offerManagerReverse.addTokenAddressToAllowList([ZERO_ADDRESS]); 43 | } 44 | 45 | // We recommend this pattern to be able to use async/await everywhere 46 | // and properly handle errors. 47 | main().catch((error) => { 48 | console.error(error); 49 | process.exitCode = 1; 50 | }); 51 | -------------------------------------------------------------------------------- /contracts/scripts/deployPoseidon.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | 3 | // Deploy a Poseidon contract for testing. 4 | async function main() { 5 | const Poseidon = await ethers.getContractFactory("GoldilocksPoseidon"); 6 | const poseidon = await Poseidon.deploy(); 7 | 8 | console.log(`Deploy a Poseidon contract: ${poseidon.address}`); 9 | } 10 | 11 | // We recommend this pattern to be able to use async/await everywhere 12 | // and properly handle errors. 13 | main().catch((error) => { 14 | console.error(error); 15 | process.exitCode = 1; 16 | }); 17 | -------------------------------------------------------------------------------- /contracts/scripts/deployV1.ts: -------------------------------------------------------------------------------- 1 | import { ethers, upgrades } from "hardhat"; 2 | 3 | async function main() { 4 | const OfferManager = await ethers.getContractFactory("OfferManager"); 5 | const offerManager = await upgrades.deployProxy(OfferManager); 6 | 7 | await offerManager.deployed(); 8 | 9 | console.log(`Deploy a OfferManager contract: ${offerManager.address}`); 10 | 11 | const OfferManagerReverse = await ethers.getContractFactory( 12 | "OfferManagerReverse" 13 | ); 14 | const offerManagerReverse = await upgrades.deployProxy(OfferManagerReverse); 15 | 16 | await offerManagerReverse.deployed(); 17 | 18 | console.log( 19 | `Deploy a OfferManagerReverse contract: ${offerManagerReverse.address}` 20 | ); 21 | } 22 | 23 | // We recommend this pattern to be able to use async/await everywhere 24 | // and properly handle errors. 25 | main().catch((error) => { 26 | console.error(error); 27 | process.exitCode = 1; 28 | }); 29 | -------------------------------------------------------------------------------- /contracts/scripts/deployV2.ts: -------------------------------------------------------------------------------- 1 | import { ethers, upgrades, network } from "hardhat"; 2 | 3 | const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; 4 | 5 | async function main() { 6 | const OfferManager = await ethers.getContractFactory("OfferManagerV2"); 7 | const offerManager = await upgrades.deployProxy(OfferManager); 8 | await offerManager.deployed(); 9 | 10 | console.log(`Deploy a OfferManager contract: ${offerManager.address}`); 11 | 12 | const OfferManagerReverse = await ethers.getContractFactory( 13 | "OfferManagerReverseV2" 14 | ); 15 | const offerManagerReverse = await upgrades.deployProxy(OfferManagerReverse); 16 | await offerManagerReverse.deployed(); 17 | 18 | console.log( 19 | `Deploy a OfferManagerReverse contract: ${offerManagerReverse.address}` 20 | ); 21 | 22 | { 23 | const owner = await offerManager.owner(); 24 | console.log("owner:", owner); 25 | } 26 | { 27 | const owner = await offerManagerReverse.owner(); 28 | console.log("owner:", owner); 29 | } 30 | 31 | let networkIndex; 32 | switch (network.name) { 33 | case "scrollalpha": 34 | networkIndex = 35 | "0x0000000000000000000000000000000000000000000000000000000000000001"; 36 | break; 37 | case "polygonzkevmtest": 38 | networkIndex = 39 | "0x0000000000000000000000000000000000000000000000000000000000000002"; 40 | break; 41 | default: 42 | networkIndex = 43 | "0x0000000000000000000000000000000000000000000000000000000000000002"; 44 | } 45 | console.log("networkIndex:", networkIndex); 46 | const Verifier = await ethers.getContractFactory("SimpleVerifier"); 47 | const verifier = await upgrades.deployProxy(Verifier, [networkIndex]); 48 | 49 | console.log(`Deploy a Verifier contract: ${verifier.address}`); 50 | 51 | await offerManager.changeVerifier(verifier.address); 52 | await offerManagerReverse.changeVerifier(verifier.address); 53 | await offerManager.addTokenAddressToAllowList([ZERO_ADDRESS]); 54 | await offerManagerReverse.addTokenAddressToAllowList([ZERO_ADDRESS]); 55 | } 56 | 57 | // We recommend this pattern to be able to use async/await everywhere 58 | // and properly handle errors. 59 | main().catch((error) => { 60 | console.error(error); 61 | process.exitCode = 1; 62 | }); 63 | -------------------------------------------------------------------------------- /contracts/scripts/updateAllowList.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | 3 | const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; 4 | 5 | async function main() { 6 | let tokenAddress = process.env.TEST_TOKEN; 7 | if (!tokenAddress) { 8 | const Erc20 = await ethers.getContractFactory("ERC20Test"); 9 | const testToken = await Erc20.deploy(); 10 | await testToken.deployed(); 11 | 12 | console.log(`Deploy a ERC20 contract: ${testToken.address}`); 13 | 14 | tokenAddress = testToken.address; 15 | } 16 | 17 | const offerManagerProxyAddress = process.env.OFFER_MANAGER_PROXY!; 18 | const offerManagerReverseProxyAddress = 19 | process.env.OFFER_MANAGER_REVERSE_PROXY!; 20 | 21 | const OfferManager = await ethers.getContractFactory("OfferManagerV2"); 22 | const offerManager = OfferManager.attach(offerManagerProxyAddress); 23 | await offerManager.deployed(); 24 | 25 | console.log(`Attach a OfferManager contract: ${offerManager.address}`); 26 | 27 | { 28 | const owner = await offerManager.owner(); 29 | console.log("owner:", owner); 30 | } 31 | 32 | await offerManager.addTokenAddressToAllowList([ZERO_ADDRESS, tokenAddress]); 33 | // await offerManager.removeTokenAddressFromAllowList([ZERO_ADDRESS]); 34 | 35 | const OfferManagerReverse = await ethers.getContractFactory( 36 | "OfferManagerReverseV2" 37 | ); 38 | const offerManagerReverse = OfferManagerReverse.attach( 39 | offerManagerReverseProxyAddress 40 | ); 41 | await offerManagerReverse.deployed(); 42 | 43 | console.log( 44 | `Attach a OfferManagerReverse contract: ${offerManagerReverse.address}` 45 | ); 46 | 47 | { 48 | const owner = await offerManagerReverse.owner(); 49 | console.log("owner:", owner); 50 | } 51 | 52 | await offerManagerReverse.addTokenAddressToAllowList([ 53 | ZERO_ADDRESS, 54 | tokenAddress, 55 | ]); 56 | // await offerManagerReverse.removeTokenAddressFromAllowList([ZERO_ADDRESS]); 57 | } 58 | 59 | // We recommend this pattern to be able to use async/await everywhere 60 | // and properly handle errors. 61 | main().catch((error) => { 62 | console.error(error); 63 | process.exitCode = 1; 64 | }); 65 | -------------------------------------------------------------------------------- /contracts/scripts/upgradeModifiedV2.ts: -------------------------------------------------------------------------------- 1 | import { ethers, upgrades } from "hardhat"; 2 | 3 | // `OFFER_MANAGER_PROXY=
OFFER_MANAGER_REVERSE_PROXY=
npx hardhat run ./scripts/upgradeV2.ts --network ` 4 | async function main() { 5 | const offerManagerProxyAddress = process.env.OFFER_MANAGER_PROXY!; 6 | const OfferManager = await ethers.getContractFactory("OfferManagerV2"); 7 | const offerManager = await upgrades.upgradeProxy( 8 | offerManagerProxyAddress, 9 | OfferManager 10 | ); 11 | 12 | await offerManager.deployed(); 13 | 14 | console.log(`Upgrade a OfferManager contract: ${offerManager.address}`); 15 | 16 | const offerManagerReverseProxyAddress = 17 | process.env.OFFER_MANAGER_REVERSE_PROXY!; 18 | const OfferManagerReverse = await ethers.getContractFactory( 19 | "OfferManagerReverseV2" 20 | ); 21 | const offerManagerReverse = await upgrades.upgradeProxy( 22 | offerManagerReverseProxyAddress, 23 | OfferManagerReverse 24 | ); 25 | 26 | await offerManagerReverse.deployed(); 27 | 28 | console.log( 29 | `Upgrade a OfferManagerReverse contract: ${offerManagerReverse.address}` 30 | ); 31 | 32 | { 33 | const owner = await offerManager.owner(); 34 | console.log("owner:", owner); 35 | } 36 | { 37 | const owner = await offerManagerReverse.owner(); 38 | console.log("owner:", owner); 39 | } 40 | } 41 | 42 | // We recommend this pattern to be able to use async/await everywhere 43 | // and properly handle errors. 44 | main().catch((error) => { 45 | console.error(error); 46 | process.exitCode = 1; 47 | }); 48 | -------------------------------------------------------------------------------- /contracts/scripts/upgradeV2.ts: -------------------------------------------------------------------------------- 1 | import { ethers, upgrades, network } from "hardhat"; 2 | 3 | // `OFFER_MANAGER_PROXY=
OFFER_MANAGER_REVERSE_PROXY=
npx hardhat run ./scripts/upgradeV2.ts --network ` 4 | async function main() { 5 | const offerManagerProxyAddress = process.env.OFFER_MANAGER_PROXY!; 6 | const [deployer] = await ethers.getSigners(); 7 | const OfferManager = await ethers.getContractFactory("OfferManagerV2"); 8 | const offerManager = await upgrades.upgradeProxy( 9 | offerManagerProxyAddress, 10 | OfferManager, 11 | { 12 | call: { 13 | fn: "initializeV2", 14 | args: [deployer.address], 15 | }, 16 | } 17 | ); 18 | 19 | await offerManager.deployed(); 20 | 21 | console.log(`Upgrade a OfferManager contract: ${offerManager.address}`); 22 | 23 | const offerManagerReverseProxyAddress = 24 | process.env.OFFER_MANAGER_REVERSE_PROXY!; 25 | const OfferManagerReverse = await ethers.getContractFactory( 26 | "OfferManagerReverseV2" 27 | ); 28 | const offerManagerReverse = await upgrades.upgradeProxy( 29 | offerManagerReverseProxyAddress, 30 | OfferManagerReverse 31 | ); 32 | 33 | await offerManagerReverse.deployed(); 34 | 35 | console.log( 36 | `Upgrade a OfferManagerReverse contract: ${offerManagerReverse.address}` 37 | ); 38 | 39 | { 40 | const owner = await offerManager.owner(); 41 | console.log("owner:", owner); 42 | } 43 | { 44 | const owner = await offerManagerReverse.owner(); 45 | console.log("owner:", owner); 46 | } 47 | 48 | let networkIndex; 49 | switch (network.name) { 50 | case "scrollalpha": 51 | networkIndex = 52 | "0x0000000000000000000000000000000000000000000000000000000000000001"; 53 | break; 54 | case "polygonzkevmtest": 55 | networkIndex = 56 | "0x0000000000000000000000000000000000000000000000000000000000000002"; 57 | break; 58 | default: 59 | networkIndex = 60 | "0x0000000000000000000000000000000000000000000000000000000000000002"; 61 | } 62 | console.log("networkIndex:", networkIndex); 63 | const Verifier = await ethers.getContractFactory("SimpleVerifier"); 64 | const verifier = await upgrades.deployProxy(Verifier, [networkIndex]); 65 | 66 | await verifier.deployed(); 67 | 68 | console.log(`Deploy a Verifier contract: ${verifier.address}`); 69 | 70 | await offerManager.changeVerifier(verifier.address); 71 | await offerManagerReverse.changeVerifier(verifier.address); 72 | } 73 | 74 | // We recommend this pattern to be able to use async/await everywhere 75 | // and properly handle errors. 76 | main().catch((error) => { 77 | console.error(error); 78 | process.exitCode = 1; 79 | }); 80 | -------------------------------------------------------------------------------- /contracts/test/OfferManager.ts: -------------------------------------------------------------------------------- 1 | import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; 2 | import { expect } from "chai"; 3 | import { ethers } from "hardhat"; 4 | 5 | describe("OfferManager", function () { 6 | // We define a fixture to reuse the same setup in every test. 7 | // We use loadFixture to run this setup once, snapshot that state, 8 | // and reset Hardhat Network to that snapshot in every test. 9 | async function deployOfferManager() { 10 | // Contracts are deployed using the first signer/account by default 11 | const [owner, maker, taker] = await ethers.getSigners(); 12 | 13 | const OfferManager = await ethers.getContractFactory("OfferManager"); 14 | const offerManager = await OfferManager.deploy(); 15 | await offerManager.initialize(); 16 | 17 | return { offerManager, owner, maker, taker }; 18 | } 19 | 20 | const sampleOffer = { 21 | makerIntmaxAddress: 22 | "0x0000000000000000000000000000000000000000000000000000000000000001", 23 | makerAssetId: 24 | "0x0000000000000000000000000000000000000000000000000000000000000001", 25 | makerAmount: 100, 26 | takerIntmaxAddress: 27 | "0x0000000000000000000000000000000000000000000000000000000000000002", 28 | takerTokenAddress: "0x0000000000000000000000000000000000000000", // ETH 29 | takerAmount: ethers.utils.parseEther("0.0001"), 30 | }; 31 | 32 | describe("Deployment", function () { 33 | it("Should return the valid next offer ID", async function () { 34 | const { offerManager } = await loadFixture(deployOfferManager); 35 | 36 | expect(await offerManager.nextOfferId()).to.equal(0); 37 | }); 38 | }); 39 | 40 | describe("Register", function () { 41 | it("Should execute without errors", async function () { 42 | const { offerManager, maker, taker } = await loadFixture( 43 | deployOfferManager 44 | ); 45 | 46 | const { 47 | makerIntmaxAddress, 48 | makerAssetId, 49 | makerAmount, 50 | takerIntmaxAddress, 51 | takerTokenAddress, 52 | takerAmount, 53 | } = sampleOffer; 54 | const offerId = 0; 55 | 56 | await expect( 57 | offerManager 58 | .connect(maker) 59 | .register( 60 | makerIntmaxAddress, 61 | makerAssetId, 62 | makerAmount, 63 | taker.address, 64 | takerIntmaxAddress, 65 | takerTokenAddress, 66 | takerAmount 67 | ) 68 | ) 69 | .to.emit(offerManager, "OfferTakerUpdated") 70 | .withArgs(offerId, takerIntmaxAddress); 71 | }); 72 | }); 73 | 74 | describe("Update taker", function () { 75 | it("Should execute without errors", async function () { 76 | const { offerManager, maker, taker } = await loadFixture( 77 | deployOfferManager 78 | ); 79 | 80 | const { 81 | makerIntmaxAddress, 82 | makerAssetId, 83 | makerAmount, 84 | takerIntmaxAddress, 85 | takerTokenAddress, 86 | takerAmount, 87 | } = sampleOffer; 88 | 89 | await offerManager 90 | .connect(maker) 91 | .register( 92 | makerIntmaxAddress, 93 | makerAssetId, 94 | makerAmount, 95 | taker.address, 96 | takerIntmaxAddress, 97 | takerTokenAddress, 98 | takerAmount 99 | ); 100 | 101 | const offerId = 0; 102 | const newTakerIntmaxAddress = 103 | "0x0000000000000000000000000000000000000000000000000000000000000003"; 104 | 105 | await expect( 106 | offerManager.connect(maker).updateTaker(offerId, newTakerIntmaxAddress) 107 | ) 108 | .to.emit(offerManager, "OfferTakerUpdated") 109 | .withArgs(offerId, newTakerIntmaxAddress); 110 | }); 111 | }); 112 | 113 | describe("Activate", function () { 114 | it("Should execute without errors", async function () { 115 | const { offerManager, maker, taker } = await loadFixture( 116 | deployOfferManager 117 | ); 118 | 119 | const { 120 | makerIntmaxAddress, 121 | makerAssetId, 122 | makerAmount, 123 | takerIntmaxAddress, 124 | takerTokenAddress, 125 | takerAmount, 126 | } = sampleOffer; 127 | 128 | await offerManager 129 | .connect(maker) 130 | .register( 131 | makerIntmaxAddress, 132 | makerAssetId, 133 | makerAmount, 134 | taker.address, 135 | takerIntmaxAddress, 136 | takerTokenAddress, 137 | takerAmount 138 | ); 139 | 140 | const offerId = 0; 141 | 142 | await expect( 143 | offerManager.connect(taker).activate(offerId, { value: takerAmount }) 144 | ) 145 | .to.emit(offerManager, "OfferActivated") 146 | .withArgs(offerId, takerIntmaxAddress); 147 | }); 148 | }); 149 | }); 150 | 151 | describe("OfferManagerTest", function () { 152 | // We define a fixture to reuse the same setup in every test. 153 | // We use loadFixture to run this setup once, snapshot that state, 154 | // and reset Hardhat Network to that snapshot in every test. 155 | async function deployOfferManager() { 156 | // Contracts are deployed using the first signer/account by default 157 | const [owner, otherAccount] = await ethers.getSigners(); 158 | 159 | const OfferManager = await ethers.getContractFactory("OfferManagerTest"); 160 | const offerManager = await OfferManager.deploy(); 161 | 162 | return { offerManager, owner, otherAccount }; 163 | } 164 | 165 | describe("Deployment", function () { 166 | it("Should return the valid next offer ID", async function () { 167 | const { offerManager } = await loadFixture(deployOfferManager); 168 | 169 | expect(await offerManager.nextOfferId()).to.equal(0); 170 | }); 171 | }); 172 | }); 173 | -------------------------------------------------------------------------------- /contracts/test/OfferManagerReverse.ts: -------------------------------------------------------------------------------- 1 | import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; 2 | import { expect } from "chai"; 3 | import { ethers } from "hardhat"; 4 | 5 | describe("OfferManagerReverse", function () { 6 | // We define a fixture to reuse the same setup in every test. 7 | // We use loadFixture to run this setup once, snapshot that state, 8 | // and reset Hardhat Network to that snapshot in every test. 9 | async function deployOfferManager() { 10 | // Contracts are deployed using the first signer/account by default 11 | const [owner, maker, taker] = await ethers.getSigners(); 12 | 13 | const OfferManagerReverse = await ethers.getContractFactory( 14 | "OfferManagerReverse" 15 | ); 16 | const offerManagerReverse = await OfferManagerReverse.deploy(); 17 | await offerManagerReverse.initialize(); 18 | 19 | return { offerManagerReverse, owner, maker, taker }; 20 | } 21 | 22 | describe("Deployment", function () { 23 | it("Should return the valid next offer ID", async function () { 24 | const { offerManagerReverse } = await loadFixture(deployOfferManager); 25 | 26 | expect(await offerManagerReverse.nextOfferId()).to.equal(0); 27 | }); 28 | }); 29 | 30 | describe("Register", function () { 31 | it("Should register a new offer", async function () { 32 | const { offerManagerReverse, maker, taker } = await loadFixture( 33 | deployOfferManager 34 | ); 35 | 36 | // Set up the variables for the test. 37 | const takerIntmaxAddress = [ 38 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39 | 17, 74, 242, 125, 0, 177, 194, 211, 40 | ]; 41 | const takerTokenAddress = "0x0000000000000000000000000000000000000000"; 42 | const takerAmount = 1000000000000000; 43 | // const makerIntmaxAddress = [ 44 | // 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45 | // 60, 24, 169, 120, 108, 176, 179, 89, 46 | // ]; 47 | const makerAssetId = "13114056477907499194"; 48 | const makerAmount = 1; 49 | 50 | // Call the register function on the offer manager contract. 51 | // Verify that the transaction was successful. 52 | await expect( 53 | offerManagerReverse 54 | .connect(taker) 55 | .register( 56 | takerIntmaxAddress, 57 | takerTokenAddress, 58 | takerAmount, 59 | maker.address, 60 | makerAssetId, 61 | makerAmount, 62 | { value: takerAmount } 63 | ) 64 | ).not.to.be.reverted; 65 | }); 66 | }); 67 | 68 | describe("Check witness", function () { 69 | it("Should activate an offer", async function () { 70 | const { offerManagerReverse, owner, maker, taker } = await loadFixture( 71 | deployOfferManager 72 | ); 73 | 74 | // Set up the variables for the test. 75 | const takerIntmaxAddress = Buffer.from( 76 | "000000000000000000000000000000000000000000000000dc88d9a25da6c75c", 77 | "hex" 78 | ); 79 | const takerTokenAddress = "0x0000000000000000000000000000000000000000"; 80 | const takerAmount = 1000000000000000; 81 | const makerAssetId = "15891190576555935580"; 82 | const makerAmount = 1; 83 | 84 | // Call the register function on the offer manager contract. 85 | // Verify that the transaction was successful. 86 | await expect( 87 | offerManagerReverse 88 | .connect(taker) 89 | .register( 90 | takerIntmaxAddress, 91 | takerTokenAddress, 92 | takerAmount, 93 | maker.address, 94 | makerAssetId, 95 | makerAmount, 96 | { value: takerAmount } 97 | ) 98 | ).not.to.be.reverted; 99 | 100 | const witness = await owner.signMessage(takerIntmaxAddress); 101 | const signerAddress = ethers.utils.verifyMessage( 102 | takerIntmaxAddress, 103 | witness 104 | ); 105 | if (owner.address !== signerAddress) { 106 | throw new Error("fail to recover signer"); 107 | } 108 | 109 | // const witness = 110 | // "0x9167b390c16fcded1e50302d869a35ea7d6248db2792af7c7f83ed796e882b797c4d70278cc833582d8dc5df06b49ca58edba26d66e586850044f3aa427aa6851b"; 111 | await expect(offerManagerReverse.connect(maker).checkWitness(0, witness)) 112 | .not.to.be.reverted; 113 | }); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /contracts/test/SimpleVerifier.ts: -------------------------------------------------------------------------------- 1 | import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; 2 | import { expect } from "chai"; 3 | import { ethers } from "hardhat"; 4 | import { 5 | assetStructType, 6 | blockHeaderStructType, 7 | merkleProofStructType, 8 | sampleWitness, 9 | } from "./sampleData"; 10 | 11 | describe("SimpleVerifier", function () { 12 | async function deployVerifier() { 13 | // Contracts are deployed using the first signer/account by default 14 | const [owner, other] = await ethers.getSigners(); 15 | 16 | const { recipient } = sampleWitness; 17 | const networkIndex = recipient; 18 | 19 | const Verifier = await ethers.getContractFactory("SimpleVerifierTest"); 20 | const verifier = await Verifier.deploy(networkIndex); 21 | 22 | return { verifier, networkIndex, owner, other }; 23 | } 24 | 25 | describe("verifyAsset", function () { 26 | describe("success", function () { 27 | it("Should execute without errors", async function () { 28 | const { verifier, networkIndex, owner } = await loadFixture( 29 | deployVerifier 30 | ); 31 | 32 | const { 33 | diffTreeInclusionProof, 34 | blockHeader, 35 | tokenAddress, 36 | tokenId, 37 | tokenAmount, 38 | } = sampleWitness; 39 | 40 | const recipient = networkIndex; 41 | const asset = { 42 | tokenAddress, 43 | tokenId, 44 | amount: tokenAmount, 45 | }; 46 | 47 | const abiEncoder = new ethers.utils.AbiCoder(); 48 | const message = abiEncoder.encode( 49 | [ 50 | `${assetStructType}[]`, 51 | "bytes32", 52 | merkleProofStructType, 53 | blockHeaderStructType, 54 | ], 55 | [[asset], recipient, diffTreeInclusionProof, blockHeader] 56 | ); 57 | 58 | const messageBytes = Buffer.from(message.slice(2), "hex"); 59 | const signature = await owner.signMessage(messageBytes); 60 | 61 | const witness = abiEncoder.encode( 62 | [ 63 | `${assetStructType}[]`, 64 | "bytes32", 65 | merkleProofStructType, 66 | blockHeaderStructType, 67 | "bytes", 68 | ], 69 | [[asset], recipient, diffTreeInclusionProof, blockHeader, signature] 70 | ); 71 | 72 | expect( 73 | await verifier.verifyAssets([asset], recipient, witness) 74 | ).to.be.equals(true); 75 | }); 76 | it("some assets", async function () { 77 | const { verifier, networkIndex, owner } = await loadFixture( 78 | deployVerifier 79 | ); 80 | 81 | const { 82 | diffTreeInclusionProof, 83 | blockHeader, 84 | tokenAddress, 85 | tokenId, 86 | tokenAmount, 87 | tokenAddress2, 88 | tokenId2, 89 | tokenAmount2, 90 | } = sampleWitness; 91 | 92 | const recipient = networkIndex; 93 | const asset = { 94 | tokenAddress, 95 | tokenId, 96 | amount: tokenAmount, 97 | }; 98 | const asset2 = { 99 | tokenAddress: tokenAddress2, 100 | tokenId: tokenId2, 101 | amount: tokenAmount2, 102 | }; 103 | const abiEncoder = new ethers.utils.AbiCoder(); 104 | const message = abiEncoder.encode( 105 | [ 106 | `${assetStructType}[]`, 107 | "bytes32", 108 | merkleProofStructType, 109 | blockHeaderStructType, 110 | ], 111 | [[asset, asset2], recipient, diffTreeInclusionProof, blockHeader] 112 | ); 113 | 114 | const messageBytes = Buffer.from(message.slice(2), "hex"); 115 | const signature = await owner.signMessage(messageBytes); 116 | 117 | const witness = abiEncoder.encode( 118 | [ 119 | `${assetStructType}[]`, 120 | "bytes32", 121 | merkleProofStructType, 122 | blockHeaderStructType, 123 | "bytes", 124 | ], 125 | [[asset, asset2], recipient, diffTreeInclusionProof, blockHeader, signature] 126 | ); 127 | 128 | expect( 129 | await verifier.verifyAssets([asset, asset2], recipient, witness) 130 | ).to.be.equals(true); 131 | }); 132 | }); 133 | describe("fail", function () { 134 | it("illegal signature", async function () { 135 | const { verifier, networkIndex, other } = await loadFixture( 136 | deployVerifier 137 | ); 138 | 139 | const { 140 | diffTreeInclusionProof, 141 | blockHeader, 142 | tokenAddress, 143 | tokenId, 144 | tokenAmount, 145 | // nonce, 146 | // recipientMerkleSiblings, 147 | } = sampleWitness; 148 | 149 | const recipient = networkIndex; 150 | const asset = { 151 | tokenAddress, 152 | tokenId, 153 | amount: tokenAmount, 154 | }; 155 | 156 | const abiEncoder = new ethers.utils.AbiCoder(); 157 | const message = abiEncoder.encode( 158 | [ 159 | `${assetStructType}[]`, 160 | "bytes32", 161 | merkleProofStructType, 162 | blockHeaderStructType, 163 | ], 164 | [[asset], recipient, diffTreeInclusionProof, blockHeader] 165 | ); 166 | 167 | const messageBytes = Buffer.from(message.slice(2), "hex"); 168 | const signature = await other.signMessage(messageBytes); 169 | 170 | const witness = abiEncoder.encode( 171 | [ 172 | `${assetStructType}[]`, 173 | "bytes32", 174 | merkleProofStructType, 175 | blockHeaderStructType, 176 | "bytes", 177 | ], 178 | [[asset], recipient, diffTreeInclusionProof, blockHeader, signature] 179 | ); 180 | await expect( 181 | verifier.verifyAssets([asset], recipient, witness) 182 | ).to.be.revertedWith("Fail to verify aggregator's signature."); 183 | }); 184 | it("illegal token address", async function () { 185 | const { verifier, networkIndex, owner } = await loadFixture( 186 | deployVerifier 187 | ); 188 | 189 | const { 190 | diffTreeInclusionProof, 191 | blockHeader, 192 | tokenAddress, 193 | tokenId, 194 | tokenAmount, 195 | tokenAddress2, 196 | tokenAddress3, 197 | tokenId2, 198 | tokenAmount2, 199 | } = sampleWitness; 200 | 201 | const recipient = networkIndex; 202 | const asset = { 203 | tokenAddress, 204 | tokenId, 205 | amount: tokenAmount, 206 | }; 207 | const asset2 = { 208 | tokenAddress: tokenAddress2, 209 | tokenId: tokenId2, 210 | amount: tokenAmount2, 211 | }; 212 | const abiEncoder = new ethers.utils.AbiCoder(); 213 | const message = abiEncoder.encode( 214 | [ 215 | `${assetStructType}[]`, 216 | "bytes32", 217 | merkleProofStructType, 218 | blockHeaderStructType, 219 | ], 220 | [[asset, asset2], recipient, diffTreeInclusionProof, blockHeader] 221 | ); 222 | 223 | const messageBytes = Buffer.from(message.slice(2), "hex"); 224 | const signature = await owner.signMessage(messageBytes); 225 | 226 | const witness = abiEncoder.encode( 227 | [ 228 | `${assetStructType}[]`, 229 | "bytes32", 230 | merkleProofStructType, 231 | blockHeaderStructType, 232 | "bytes", 233 | ], 234 | [[asset, asset2], recipient, diffTreeInclusionProof, blockHeader, signature] 235 | ); 236 | const dummyAsset2 = { 237 | tokenAddress: tokenAddress3, 238 | tokenId: tokenId2, 239 | amount: tokenAmount2, 240 | }; 241 | await expect( 242 | verifier.verifyAssets([asset, dummyAsset2], recipient, witness) 243 | ).to.be.revertedWith("Not same asset: tokenAddress"); 244 | }); 245 | it("illegal token id", async function () { 246 | const { verifier, networkIndex, owner } = await loadFixture( 247 | deployVerifier 248 | ); 249 | 250 | const { 251 | diffTreeInclusionProof, 252 | blockHeader, 253 | tokenAddress, 254 | tokenId, 255 | tokenAmount, 256 | tokenAddress2, 257 | tokenId2, 258 | tokenId3, 259 | tokenAmount2, 260 | } = sampleWitness; 261 | 262 | const recipient = networkIndex; 263 | const asset = { 264 | tokenAddress, 265 | tokenId, 266 | amount: tokenAmount, 267 | }; 268 | const asset2 = { 269 | tokenAddress: tokenAddress2, 270 | tokenId: tokenId2, 271 | amount: tokenAmount2, 272 | }; 273 | const abiEncoder = new ethers.utils.AbiCoder(); 274 | const message = abiEncoder.encode( 275 | [ 276 | `${assetStructType}[]`, 277 | "bytes32", 278 | merkleProofStructType, 279 | blockHeaderStructType, 280 | ], 281 | [[asset, asset2], recipient, diffTreeInclusionProof, blockHeader] 282 | ); 283 | 284 | const messageBytes = Buffer.from(message.slice(2), "hex"); 285 | const signature = await owner.signMessage(messageBytes); 286 | 287 | const witness = abiEncoder.encode( 288 | [ 289 | `${assetStructType}[]`, 290 | "bytes32", 291 | merkleProofStructType, 292 | blockHeaderStructType, 293 | "bytes", 294 | ], 295 | [[asset, asset2], recipient, diffTreeInclusionProof, blockHeader, signature] 296 | ); 297 | const dummyAsset2 = { 298 | tokenAddress: tokenAddress2, 299 | tokenId: tokenId3, 300 | amount: tokenAmount2, 301 | }; 302 | await expect( 303 | verifier.verifyAssets([asset, dummyAsset2], recipient, witness) 304 | ).to.be.revertedWith("Not same asset: tokenId"); 305 | }); 306 | it("illegal amount", async function () { 307 | const { verifier, networkIndex, owner } = await loadFixture( 308 | deployVerifier 309 | ); 310 | 311 | const { 312 | diffTreeInclusionProof, 313 | blockHeader, 314 | tokenAddress, 315 | tokenId, 316 | tokenAmount, 317 | tokenAddress2, 318 | tokenId2, 319 | tokenAmount2, 320 | tokenAmount3 321 | } = sampleWitness; 322 | 323 | const recipient = networkIndex; 324 | const asset = { 325 | tokenAddress, 326 | tokenId, 327 | amount: tokenAmount, 328 | }; 329 | const asset2 = { 330 | tokenAddress: tokenAddress2, 331 | tokenId: tokenId2, 332 | amount: tokenAmount2, 333 | }; 334 | const abiEncoder = new ethers.utils.AbiCoder(); 335 | const message = abiEncoder.encode( 336 | [ 337 | `${assetStructType}[]`, 338 | "bytes32", 339 | merkleProofStructType, 340 | blockHeaderStructType, 341 | ], 342 | [[asset, asset2], recipient, diffTreeInclusionProof, blockHeader] 343 | ); 344 | 345 | const messageBytes = Buffer.from(message.slice(2), "hex"); 346 | const signature = await owner.signMessage(messageBytes); 347 | 348 | const witness = abiEncoder.encode( 349 | [ 350 | `${assetStructType}[]`, 351 | "bytes32", 352 | merkleProofStructType, 353 | blockHeaderStructType, 354 | "bytes", 355 | ], 356 | [[asset, asset2], recipient, diffTreeInclusionProof, blockHeader, signature] 357 | ); 358 | const dummyAsset2 = { 359 | tokenAddress: tokenAddress2, 360 | tokenId: tokenId2, 361 | amount: tokenAmount3, 362 | }; 363 | await expect( 364 | verifier.verifyAssets([asset, dummyAsset2], recipient, witness) 365 | ).to.be.revertedWith("Not same asset: amount"); 366 | }); 367 | it("Not same recipient", async function () { 368 | const { verifier, networkIndex, owner } = await loadFixture( 369 | deployVerifier 370 | ); 371 | 372 | const { 373 | diffTreeInclusionProof, 374 | blockHeader, 375 | tokenAddress, 376 | tokenId, 377 | tokenAmount, 378 | recipient2 379 | } = sampleWitness; 380 | 381 | const recipient = networkIndex; 382 | const asset = { 383 | tokenAddress, 384 | tokenId, 385 | amount: tokenAmount, 386 | }; 387 | 388 | const abiEncoder = new ethers.utils.AbiCoder(); 389 | const message = abiEncoder.encode( 390 | [ 391 | `${assetStructType}[]`, 392 | "bytes32", 393 | merkleProofStructType, 394 | blockHeaderStructType, 395 | ], 396 | [[asset], recipient, diffTreeInclusionProof, blockHeader] 397 | ); 398 | 399 | const messageBytes = Buffer.from(message.slice(2), "hex"); 400 | const signature = await owner.signMessage(messageBytes); 401 | 402 | const witness = abiEncoder.encode( 403 | [ 404 | `${assetStructType}[]`, 405 | "bytes32", 406 | merkleProofStructType, 407 | blockHeaderStructType, 408 | "bytes", 409 | ], 410 | [[asset], recipient, diffTreeInclusionProof, blockHeader, signature] 411 | ); 412 | await expect( 413 | verifier.verifyAssets([asset], recipient2, witness) 414 | ).to.be.revertedWith("Not same recipient"); 415 | }); 416 | }); 417 | }); 418 | }); 419 | 420 | -------------------------------------------------------------------------------- /contracts/test/Verifier.ts: -------------------------------------------------------------------------------- 1 | import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; 2 | import { expect } from "chai"; 3 | import { ethers } from "hardhat"; 4 | import { 5 | blockHeaderStructType, 6 | merkleProofStructType, 7 | sampleWitness, 8 | } from "./sampleData"; 9 | 10 | describe("Verifier", function () { 11 | async function deployVerifier() { 12 | // Contracts are deployed using the first signer/account by default 13 | const [owner] = await ethers.getSigners(); 14 | 15 | const { recipient } = sampleWitness; 16 | const networkIndex = recipient; 17 | 18 | const Verifier = await ethers.getContractFactory("VerifierTest"); 19 | const verifier = await Verifier.deploy(networkIndex); 20 | 21 | return { verifier, networkIndex, owner }; 22 | } 23 | 24 | describe("verify", function () { 25 | it("Should execute without errors", async function () { 26 | const { verifier, networkIndex, owner } = await loadFixture( 27 | deployVerifier 28 | ); 29 | 30 | const { 31 | diffTreeInclusionProof, 32 | blockHeader, 33 | tokenAddress, 34 | tokenId, 35 | tokenAmount, 36 | nonce, 37 | recipientMerkleSiblings, 38 | } = sampleWitness; 39 | 40 | const recipient = networkIndex; 41 | const asset = { 42 | tokenAddress, 43 | tokenId, 44 | amount: tokenAmount, 45 | }; 46 | 47 | const messageBytes = Buffer.from(sampleWitness.blockHash.slice(2), "hex"); 48 | const signature = await owner.signMessage(messageBytes); 49 | await verifier.updateTransactionsDigest(blockHeader, signature); 50 | 51 | const abiCoder = new ethers.utils.AbiCoder(); 52 | const witness = abiCoder.encode( 53 | ["bytes32", "bytes32[]", merkleProofStructType, blockHeaderStructType], 54 | [nonce, recipientMerkleSiblings, diffTreeInclusionProof, blockHeader] 55 | ); 56 | 57 | // const witness = await verifier.calcWitness( 58 | // nonce, 59 | // recipientMerkleSiblings, 60 | // diffTreeInclusionProof, 61 | // blockHeader 62 | // ); 63 | expect( 64 | await verifier.verifyAssets([asset], recipient, witness) 65 | ).to.be.equals(true); 66 | // expect(await verifier.verifyBlockHash(blockHash, signature)).to.be.equals( 67 | // true 68 | // ); 69 | }); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /contracts/test/sampleData.ts: -------------------------------------------------------------------------------- 1 | export const calcSampleWitness = () => { 2 | const diffTreeInclusionProof = { 3 | index: 0, 4 | value: "0x773dd5bf1bf7e274f11ea9c1b5fd7f6dadcc93cb50f8520f325c649d73686388", 5 | siblings: [ 6 | "0xc71603f33a1144ca7953db0ab48808f4c4055e3364a246c33c18a9786cb0b359", 7 | "0x2196fc41328ae503de8f9ad762a30af28d85581b9901b2cfb61a4ad1aaf14fcc", 8 | "0x67703a0cc73ca54246fb94bfe956c05f9a247cc59da2de6461e00af7295ce05a", 9 | "0xf522eaa0af88a040167d7cf3bf854d278cc1b30d2e2c09475154921a06462644", 10 | ], 11 | }; 12 | const blockHeader = { 13 | blockNumber: 14 | "0x000000000000000000000000000000000000000000000000000000000000000e", 15 | prevBlockHash: 16 | "0xd69e8ba062dfbdf5506cb39b278fad99072c26ba358f1352062bbe0e7797561a", 17 | blockHeadersDigest: 18 | "0x17f4f8aad4e73357da75b58398bc47a027d517948c6907149812eeb63c532e64", 19 | transactionsDigest: 20 | "0xe79eb578dbd27373cb6bbc9512fa95ea32889751315d3c9a2c3000adb72de44a", 21 | depositDigest: 22 | "0x0c421bb92255b128ad866cbbe6a59e10453add89e9ff862b007c6ac5da2df291", 23 | proposedWorldStateDigest: 24 | "0xfbcb8fd6122b9c99446784f5a3a3663252028d461c1886d9ea2cca702dca6ad1", 25 | approvedWorldStateDigest: 26 | "0xfbcb8fd6122b9c99446784f5a3a3663252028d461c1886d9ea2cca702dca6ad1", 27 | latestAccountDigest: 28 | "0xcbf6871d4eb74cec1512b284b3e9309594f123f0d0264242164b108b72ca674c", 29 | }; 30 | const blockHash = 31 | "0x93275bdc7c643a0e10757c4f650f020f10cb1f024335e1cc112039447010189f"; 32 | const recipient = 33 | "0x00000000000000000000000000000000000000000000000010d1cb00b658931e"; 34 | const recipient2 = 35 | "0x00000000000000000000000000000000000000000000000010d1cb00b658931f"; 36 | const tokenAddress = 37 | "0x000000000000000000000000000000000000000000000000f7c23e5c2d79b6ae"; 38 | const tokenAddress2 = 39 | "0x000000000000000000000000000000000000000000000000f7c23e5c2d79b6af"; 40 | const tokenAddress3 = 41 | "0x000000000000000000000000000000000000000000000000f8c23e5c2d79b6af"; 42 | const tokenId = 43 | "0x0000000000000000000000000000000000000000000000000000000000000000"; 44 | const tokenId2 = 45 | "0x0000000000000000000000000000000000000000000000000000000000000001"; 46 | const tokenId3 = 47 | "0x0000000000000000000000000000000000000000000000000000000000000099"; 48 | const tokenAmount = 3; 49 | const tokenAmount2 = 6; 50 | const tokenAmount3 = 9; 51 | const nonce = 52 | "0xa710189dc0d8eb00a46e0411c0b1965192f80c50fbd8cbd51b5c67b26fc9dff1"; 53 | const recipientMerkleSiblings = [ 54 | "0x0000000000000000000000000000000000000000000000000000000000000000", 55 | "0x0000000000000000000000000000000000000000000000000000000000000000", 56 | "0x0000000000000000000000000000000000000000000000000000000000000000", 57 | "0x0000000000000000000000000000000000000000000000000000000000000000", 58 | "0x99bb2839b542ab8bd432363cc4ebbf9d0623d34d1b2d2ff7e23b803b2ff5c94e", 59 | ]; 60 | 61 | return { 62 | diffTreeInclusionProof, 63 | blockHeader, 64 | blockHash, 65 | recipient, 66 | recipient2, 67 | tokenAddress, 68 | tokenId, 69 | tokenId2, 70 | tokenId3, 71 | tokenAmount, 72 | tokenAddress2, 73 | tokenAddress3, 74 | tokenAmount2, 75 | tokenAmount3, 76 | nonce, 77 | recipientMerkleSiblings, 78 | }; 79 | }; 80 | 81 | export const sampleWitness = calcSampleWitness(); 82 | 83 | // MerkleTreeInterface.MerkleProof 84 | export const merkleProofStructType = 85 | "tuple(uint256 index, bytes32 value, bytes32[] siblings)"; 86 | 87 | // VerifierInterface.Asset 88 | export const assetStructType = 89 | "tuple(bytes32 tokenAddress, uint256 tokenId, uint256 amount)"; 90 | 91 | // VerifierInterface.BlockHeader 92 | export const blockHeaderStructType = 93 | "tuple(uint256 blockNumber, bytes32 prevBlockHash, bytes32 blockHeadersDigest, bytes32 transactionsDigest, bytes32 depositDigest, bytes32 proposedWorldStateDigest, bytes32 approvedWorldStateDigest, bytes32 latestAccountDigest)"; 94 | 95 | -------------------------------------------------------------------------------- /contracts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /docs/address.json: -------------------------------------------------------------------------------- 1 | { 2 | "scrollalpha": { 3 | "offerManager": "0x007c969728eE4f068ceCF3405D65a037dB5BeEa1", 4 | "reverseOfferManager": "0x4ee8cB7864df06A8c7703988C15bAaAB9ac47CAe", 5 | "verifier": "0x4c8Ca3410AE74429068F22f29cD3D0EaAF1afdE1" 6 | }, 7 | "polygonzkevmtest": { 8 | "offerManager": "0x161a72Bc1b76586a36A9014Dd58d401eE2B24094", 9 | "reverseOfferManager": "0x1E316b313de98C7eCb2393995ef27715E3E1c7a7", 10 | "verifier": "0x4Ee526Eb12E534a655996e72fA275D3448CFAFf4" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /docs/concept.md: -------------------------------------------------------------------------------- 1 | # Concept 2 | 3 | If Bob sends a token to Alice on another L2 with zkEVM, a function whereby Alice sends a token to Bob on INTMAX. 4 | The network with zkEVM functionality is called network 1. 5 | Alice first burns the tokens she wants to send to Bob on INTMAX. 6 | Alice then records the amount of money she wants to receive from Bob on network 1 via the register() function. 7 | Bob confirms that Alice has correctly burnt the token on INTMAX and then sends the token that Alice wants through the activate() function. 8 | The INTMAX network then reflects this information and Bob can receive the tokens Alice has incinerated on INTMAX. 9 | 10 | ![interoperability_description](https://user-images.githubusercontent.com/20158457/227613428-4c2c962a-d016-4d9e-bbd2-910236ca7b7e.jpg) 11 | -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | # A secret key born from the Hardhat's mnemonic. 2 | # Use only for testing purposes in the local environment. 3 | PRIVATE_KEY=ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 4 | -------------------------------------------------------------------------------- /src/bin/offer_manager.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::Arc, time::Duration}; 2 | 3 | use ethers::{ 4 | core::types::{Address, U256}, 5 | middleware::SignerMiddleware, 6 | prelude::k256::ecdsa::SigningKey, 7 | providers::{Http, Provider}, 8 | signers::LocalWallet, 9 | utils::secret_key_to_address, 10 | }; 11 | use intmax_interoperability_plugin::contracts::offer_manager::OfferManagerContractWrapper; 12 | 13 | #[tokio::main] 14 | async fn main() { 15 | let rpc_url = "https://alpha-rpc.scroll.io/l2"; 16 | let chain_id = 534353u64; 17 | 18 | let provider = Provider::::try_from(rpc_url) 19 | .unwrap() 20 | .interval(Duration::from_millis(10u64)); 21 | 22 | let rng = rand::thread_rng(); 23 | let signer_key = SigningKey::random(rng); 24 | // let _ = dotenv::dotenv().ok(); 25 | // let secret_key = 26 | // std::env::var("PRIVATE_KEY").expect("PRIVATE_KEY must be set in .env file. This address must have sufficient ETH (around 0.1 ETH) on Scroll alpha to execute the transaction."); 27 | // let signer_key = SigningKey::from_bytes(&hex::decode(&secret_key).unwrap()).unwrap(); 28 | 29 | let my_account = secret_key_to_address(&signer_key); 30 | let wallet = LocalWallet::new_with_signer(signer_key, my_account, chain_id); 31 | let client = SignerMiddleware::new(provider, wallet); 32 | let client = Arc::new(client); 33 | 34 | let contract_address: Address = "0x007c969728eE4f068ceCF3405D65a037dB5BeEa1" 35 | .parse() 36 | .unwrap(); 37 | let contract = OfferManagerContractWrapper::new(contract_address, client); 38 | 39 | let next_offer_id: U256 = contract.next_offer_id().call().await.unwrap(); 40 | dbg!(next_offer_id); 41 | 42 | // // TODO: Rewriting without using the test function. 43 | // println!("start register()"); 44 | // contract 45 | // .test_register( 46 | // [0u8; 32], // maker_intmax 47 | // 1u8.into(), // maker_asset_id 48 | // 100u64.into(), // maker_amount 49 | // my_account, // taker 50 | // [0u8; 32], // taker_intmax 51 | // "0x0000000000000000000000000000000000000000" 52 | // .parse() 53 | // .unwrap(), // taker_token_address 54 | // 1u8.into(), // taker_amount 55 | // ) 56 | // .send() 57 | // .await 58 | // .unwrap(); 59 | // println!("end register()"); 60 | 61 | // let next_next_offer_id: U256 = contract.next_offer_id().await.unwrap(); 62 | // assert_eq!(next_next_offer_id, next_offer_id + U256::from(1u8)); 63 | 64 | // let is_registered = contract.is_registered(next_offer_id).await.unwrap(); 65 | // assert!(is_registered); 66 | 67 | // let logs = contract.get_register_events(vec![]).await.unwrap(); 68 | // dbg!(logs); 69 | 70 | // println!("start activate()"); 71 | // contract.test_activate(next_offer_id).send().await.unwrap(); 72 | // println!("end activate()"); 73 | 74 | // let logs = contract.get_activate_events().await.unwrap(); 75 | // dbg!(logs); 76 | 77 | // let is_activated = contract.is_activated(next_offer_id).await.unwrap(); 78 | // assert!(is_activated); 79 | } 80 | -------------------------------------------------------------------------------- /src/bin/test_poseidon_contract.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::Arc, time::Duration}; 2 | 3 | use dotenv::dotenv; 4 | use ethers::{ 5 | contract::abigen, 6 | core::types::{Address, U256}, 7 | middleware::SignerMiddleware, 8 | prelude::k256::ecdsa::SigningKey, 9 | providers::{Http, Provider}, 10 | signers::LocalWallet, 11 | utils::secret_key_to_address, 12 | }; 13 | #[cfg(feature = "test_poseidon")] 14 | use plonky2::{ 15 | field::{ 16 | goldilocks_field::GoldilocksField, 17 | types::{Field, PrimeField64, Sample}, 18 | }, 19 | hash::{ 20 | hash_types::HashOut, 21 | hashing::{compress, hash_n_to_m_no_pad}, 22 | poseidon::PoseidonPermutation, 23 | }, 24 | plonk::config::GenericHashOut, 25 | }; 26 | 27 | abigen!( 28 | PoseidonContract, 29 | "./contracts/compiled-artifacts/contracts/utils/Poseidon.sol/GoldilocksPoseidon.json" 30 | ); 31 | 32 | #[cfg(feature = "test_poseidon")] 33 | type F = GoldilocksField; 34 | 35 | #[cfg(not(feature = "test_poseidon"))] 36 | fn main() {} 37 | 38 | #[cfg(feature = "test_poseidon")] 39 | #[tokio::main] 40 | async fn main() { 41 | let _ = dotenv().ok(); 42 | let rpc_url = "http://localhost:8545"; 43 | let chain_id = 31337; 44 | 45 | let provider = Provider::::try_from(rpc_url) 46 | .unwrap() 47 | .interval(Duration::from_millis(10u64)); 48 | let rng = rand::thread_rng(); 49 | let signer_key = SigningKey::random(rng); 50 | let my_account = secret_key_to_address(&signer_key); 51 | let wallet = LocalWallet::new_with_signer(signer_key, my_account, chain_id); 52 | let client = SignerMiddleware::new(provider, wallet); 53 | let client = Arc::new(client); 54 | 55 | let contract_address: Address = std::env::var("POSEIDON_CONTRACT_ADDRESS") 56 | .expect("POSEIDON_CONTRACT_ADDRESS must be set in .env file") 57 | .parse() 58 | .unwrap(); 59 | let contract = PoseidonContract::new(contract_address, client); 60 | 61 | let mut i = 0usize; 62 | loop { 63 | if 10usize.pow(f64::log10(i as f64).ceil() as u32) == i { 64 | println!("i = {i}"); 65 | } 66 | 67 | let left = HashOut::::rand(); 68 | let right = HashOut::::rand(); 69 | let expected_output = compress::>(left, right); 70 | let mut solidity_left: [u8; 32] = left.to_bytes().try_into().unwrap(); 71 | solidity_left.reverse(); 72 | let mut solidity_right: [u8; 32] = right.to_bytes().try_into().unwrap(); 73 | solidity_right.reverse(); 74 | let mut solidity_output: [u8; 32] = contract 75 | .two_to_one(solidity_left, solidity_right) 76 | .call() 77 | .await 78 | .unwrap(); 79 | solidity_output.reverse(); 80 | let output = HashOut::::from_bytes(&solidity_output); 81 | if output != expected_output { 82 | dbg!(left, right); 83 | assert_eq!(output, expected_output); 84 | } 85 | 86 | let num_inputs = rand::random::() % 25; 87 | let input = vec![(); num_inputs] 88 | .iter() 89 | .map(|_| F::rand()) 90 | .collect::>(); 91 | let num_outputs: usize = 8; 92 | let expected_output = hash_n_to_m_no_pad::>(&input, num_outputs); 93 | let solidity_input = input 94 | .iter() 95 | .map(|v| U256::from(v.to_canonical_u64())) 96 | .collect::>(); 97 | let solidity_output: Vec = contract 98 | .hash_n_to_m_no_pad(solidity_input, num_outputs.into()) 99 | .call() 100 | .await 101 | .unwrap(); 102 | let output = solidity_output 103 | .iter() 104 | .map(|v| F::from_canonical_u64(v.as_u64())) 105 | .collect::>(); 106 | 107 | if output != expected_output { 108 | dbg!(input); 109 | assert_eq!(output, expected_output); 110 | } 111 | 112 | i += 1; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/contracts/erc20_interface.rs: -------------------------------------------------------------------------------- 1 | use ethers::contract::abigen; 2 | 3 | abigen!( 4 | Erc20Interface, 5 | "./contracts/compiled-artifacts/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol/IERC20Metadata.json" 6 | ); 7 | -------------------------------------------------------------------------------- /src/contracts/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod erc20_interface; 2 | pub mod offer_manager; 3 | pub mod offer_manager_base; 4 | pub mod offer_manager_reverse; 5 | pub mod verifier; 6 | -------------------------------------------------------------------------------- /src/contracts/offer_manager.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ethers::{ 4 | contract::{abigen, builders::Event}, 5 | core::types::{Address, U256}, 6 | providers::Middleware, 7 | types::{H160, H256}, 8 | }; 9 | 10 | abigen!( 11 | OfferManagerContract, 12 | "./contracts/compiled-artifacts/contracts/OfferManagerV2.sol/OfferManagerV2.json" 13 | ); 14 | 15 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 16 | pub struct RegisterEvent { 17 | /// topic 0 18 | pub offer_id: U256, 19 | /// topic 1 20 | pub maker: H160, 21 | 22 | pub maker_intmax_address: H256, 23 | pub maker_asset_id: U256, 24 | pub maker_amount: U256, 25 | pub taker: H160, 26 | pub taker_token_address: H160, 27 | pub taker_amount: U256, 28 | } 29 | 30 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 31 | pub struct ActivateEvent { 32 | /// topic 0 33 | pub offer_id: U256, 34 | 35 | /// topic 1 36 | pub taker: H256, 37 | } 38 | 39 | #[derive(Clone, Debug)] 40 | pub struct OfferManagerContractWrapper { 41 | pub contract: OfferManagerContract, 42 | pub address: Address, 43 | } 44 | 45 | impl std::ops::Deref for OfferManagerContractWrapper { 46 | type Target = OfferManagerContract; 47 | 48 | fn deref(&self) -> &Self::Target { 49 | &self.contract 50 | } 51 | } 52 | 53 | impl OfferManagerContractWrapper { 54 | pub fn new(contract_address: Address, client: Arc) -> Self { 55 | Self { 56 | contract: OfferManagerContract::new(contract_address, client), 57 | address: contract_address, 58 | } 59 | } 60 | 61 | pub async fn get_register_events( 62 | &self, 63 | topic_offer_id: Option>, 64 | topic_maker: Option>, 65 | latest_block: u64, 66 | ) -> anyhow::Result> { 67 | let from_block_num = if latest_block < 9900 { 68 | 0 69 | } else { 70 | latest_block - 9900 71 | }; 72 | let filter: Event = self 73 | .offer_registered_filter() 74 | .address(self.address.into()) 75 | .from_block(from_block_num); 76 | let filter = if let Some(topic_offer_id) = topic_offer_id.clone() { 77 | filter.topic1(topic_offer_id) 78 | } else { 79 | filter 80 | }; 81 | let filter = if let Some(topic_maker) = topic_maker.clone() { 82 | filter.topic2(topic_maker) 83 | } else { 84 | filter 85 | }; 86 | 87 | let logs: Vec = filter 88 | .query() 89 | .await 90 | .map_err(|err| anyhow::anyhow!("{}", err))?; 91 | println!("logs.len() = {}", logs.len()); 92 | let logs = logs 93 | .into_iter() 94 | .filter_map(|log| { 95 | { 96 | let mut bytes = [0u8; 32]; 97 | log.offer_id.to_big_endian(&mut bytes); 98 | let offer_id = H256::from(bytes); 99 | if let Some(topic_offer_id) = topic_offer_id.clone() { 100 | if !topic_offer_id.iter().any(|topic| topic == &offer_id) { 101 | return None; 102 | } 103 | } 104 | let maker = H256::from(log.maker); 105 | if let Some(topic_maker) = topic_maker.clone() { 106 | if !topic_maker.iter().any(|topic| topic == &maker) { 107 | return None; 108 | } 109 | } 110 | } 111 | 112 | Some(RegisterEvent { 113 | offer_id: log.offer_id, 114 | maker: log.maker, 115 | maker_intmax_address: H256::from(log.maker_intmax_address), 116 | maker_asset_id: log.maker_asset_id, 117 | maker_amount: log.maker_amount, 118 | taker: log.taker, 119 | taker_token_address: log.taker_token_address, 120 | taker_amount: log.taker_amount, 121 | }) 122 | }) 123 | .collect::>(); 124 | 125 | Ok(logs) 126 | } 127 | 128 | pub async fn get_activate_events( 129 | &self, 130 | latest_block: u64, 131 | ) -> anyhow::Result> { 132 | // Activate(indexed offerId, indexed takerIntmax) 133 | let from_block_num = if latest_block < 9900 { 134 | 0 135 | } else { 136 | latest_block - 9900 137 | }; 138 | 139 | let filter: Event = self 140 | .offer_activated_filter() 141 | .address(self.address.into()) 142 | .from_block(from_block_num); 143 | let logs: Vec = filter 144 | .query() 145 | .await 146 | .map_err(|err| anyhow::anyhow!("{}", err))?; 147 | println!("logs.len() = {}", logs.len()); 148 | let logs = logs 149 | .into_iter() 150 | .map(|log| ActivateEvent { 151 | offer_id: log.offer_id, 152 | taker: H256::from(log.taker_intmax_address), 153 | }) 154 | .collect::>(); 155 | 156 | Ok(logs) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/contracts/offer_manager_base.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashSet, sync::Arc}; 2 | 3 | use ethers::{ 4 | contract::{abigen, builders::Event}, 5 | core::types::Address, 6 | providers::Middleware, 7 | }; 8 | 9 | abigen!( 10 | OfferManagerBaseContract, 11 | "./contracts/compiled-artifacts/contracts/OfferManagerBaseInterface.sol/OfferManagerBaseInterface.json" 12 | ); 13 | 14 | abigen!( 15 | TokenAllowListContract, 16 | "./contracts/compiled-artifacts/contracts/utils/TokenAllowListInterface.sol/TokenAllowListInterface.json" 17 | ); 18 | 19 | #[derive(Clone, Debug)] 20 | pub struct TokenAllowListContractWrapper { 21 | pub contract: TokenAllowListContract, 22 | pub address: Address, 23 | } 24 | 25 | impl std::ops::Deref for TokenAllowListContractWrapper { 26 | type Target = TokenAllowListContract; 27 | 28 | fn deref(&self) -> &Self::Target { 29 | &self.contract 30 | } 31 | } 32 | 33 | impl TokenAllowListContractWrapper { 34 | pub fn new(contract_address: Address, client: Arc) -> Self { 35 | Self { 36 | contract: TokenAllowListContract::new(contract_address, client), 37 | address: contract_address, 38 | } 39 | } 40 | 41 | pub async fn get_token_allow_list(&self, latest_block: u64) -> anyhow::Result> { 42 | let from_block_num = if latest_block < 9900 { 43 | 0 44 | } else { 45 | latest_block - 9900 46 | }; 47 | let filter: Event = self 48 | .token_allow_list_updated_filter() 49 | .address(self.address.into()) 50 | .from_block(from_block_num); 51 | 52 | let logs: Vec = filter 53 | .query() 54 | .await 55 | .map_err(|err| anyhow::anyhow!("{}", err))?; 56 | println!("logs.len() = {}", logs.len()); 57 | let mut result = HashSet::new(); 58 | for log in logs { 59 | if log.is_allowed { 60 | result.insert(log.token); 61 | } else { 62 | result.remove(&log.token); 63 | } 64 | } 65 | 66 | let mut result = result.into_iter().collect::>(); 67 | result.sort(); 68 | 69 | Ok(result) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/contracts/offer_manager_reverse.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ethers::{ 4 | contract::{abigen, builders::Event}, 5 | core::types::{Address, U256}, 6 | providers::Middleware, 7 | types::{H160, H256}, 8 | }; 9 | 10 | abigen!( 11 | OfferManagerReverseContract, 12 | "./contracts/compiled-artifacts/contracts/OfferManagerReverseV2.sol/OfferManagerReverseV2.json" 13 | ); 14 | 15 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 16 | pub struct LockEvent { 17 | /// topic 0 18 | pub offer_id: U256, 19 | /// topic 1 20 | pub taker: H160, 21 | 22 | pub taker_intmax_address: H256, 23 | pub taker_token_address: H160, 24 | pub taker_amount: U256, 25 | pub maker_intmax_address: H256, 26 | pub maker_asset_id: U256, 27 | pub maker_amount: U256, 28 | } 29 | 30 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 31 | pub struct UnlockEvent { 32 | /// topic 0 33 | pub offer_id: U256, 34 | 35 | /// topic 1 36 | pub maker: H160, 37 | } 38 | 39 | #[derive(Clone, Debug)] 40 | pub struct OfferManagerReverseContractWrapper { 41 | pub contract: OfferManagerReverseContract, 42 | pub address: Address, 43 | } 44 | 45 | impl std::ops::Deref for OfferManagerReverseContractWrapper { 46 | type Target = OfferManagerReverseContract; 47 | 48 | fn deref(&self) -> &Self::Target { 49 | &self.contract 50 | } 51 | } 52 | 53 | impl OfferManagerReverseContractWrapper { 54 | pub fn new(contract_address: Address, client: Arc) -> Self { 55 | Self { 56 | contract: OfferManagerReverseContract::new(contract_address, client), 57 | address: contract_address, 58 | } 59 | } 60 | 61 | pub async fn get_register_events( 62 | &self, 63 | topic_offer_id: Option>, 64 | topic_taker: Option>, 65 | latest_block: u64, 66 | ) -> anyhow::Result> { 67 | let from_block_num = if latest_block < 9900 { 68 | 0 69 | } else { 70 | latest_block - 9900 71 | }; 72 | println!("from_block_num: {from_block_num}"); 73 | let filter: Event = self 74 | .offer_registered_filter() 75 | .address(self.address.into()) 76 | .from_block(from_block_num); 77 | let filter = if let Some(topic_offer_id) = topic_offer_id.clone() { 78 | filter.topic1(topic_offer_id) 79 | } else { 80 | filter 81 | }; 82 | let filter = if let Some(topic_taker) = topic_taker.clone() { 83 | filter.topic2(topic_taker) 84 | } else { 85 | filter 86 | }; 87 | 88 | let logs: Vec = filter 89 | .query() 90 | .await 91 | .map_err(|err| anyhow::anyhow!("{}", err))?; 92 | println!("logs.len() = {}", logs.len()); 93 | let logs = logs 94 | .into_iter() 95 | .filter_map(|log| { 96 | { 97 | let mut bytes = [0u8; 32]; 98 | log.offer_id.to_big_endian(&mut bytes); 99 | let offer_id = H256::from(bytes); 100 | if let Some(topic_offer_id) = topic_offer_id.clone() { 101 | if !topic_offer_id.iter().any(|topic| topic == &offer_id) { 102 | return None; 103 | } 104 | } 105 | let taker = H256::from(log.taker); 106 | if let Some(topic_taker) = topic_taker.clone() { 107 | if !topic_taker.iter().any(|topic| topic == &taker) { 108 | return None; 109 | } 110 | } 111 | } 112 | 113 | Some(LockEvent { 114 | offer_id: log.offer_id, 115 | taker: log.taker, 116 | taker_intmax_address: H256::from(log.taker_intmax_address), 117 | taker_token_address: log.taker_token_address, 118 | taker_amount: log.taker_amount, 119 | maker_intmax_address: H256::from(log.maker_intmax_address), 120 | maker_asset_id: log.maker_asset_id, 121 | maker_amount: log.maker_amount, 122 | }) 123 | }) 124 | .collect::>(); 125 | 126 | Ok(logs) 127 | } 128 | 129 | pub async fn get_activate_events(&self, latest_block: u64) -> anyhow::Result> { 130 | // Activate(indexed offerId, indexed takerIntmax) 131 | let from_block_num = if latest_block < 9900 { 132 | 0 133 | } else { 134 | latest_block - 9900 135 | }; 136 | let filter: Event = self 137 | .offer_activated_filter() 138 | .address(self.address.into()) 139 | .from_block(from_block_num); 140 | let logs: Vec = filter 141 | .query() 142 | .await 143 | .map_err(|err| anyhow::anyhow!("{}", err))?; 144 | println!("logs.len() = {}", logs.len()); 145 | let logs = logs 146 | .into_iter() 147 | .map(|log| UnlockEvent { 148 | offer_id: log.offer_id, 149 | maker: log.maker, 150 | }) 151 | .collect::>(); 152 | 153 | Ok(logs) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/contracts/verifier.rs: -------------------------------------------------------------------------------- 1 | use ethers::contract::abigen; 2 | 3 | abigen!( 4 | VerifierContract, 5 | "./contracts/compiled-artifacts/contracts/Verifier.test.sol/VerifierTest.json" 6 | ); 7 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contracts; 2 | 3 | pub extern crate ethers; 4 | --------------------------------------------------------------------------------