Skip to content

Latest commit

 

History

History
138 lines (90 loc) · 18.5 KB

README.md

File metadata and controls

138 lines (90 loc) · 18.5 KB

ChainLocker Smart Contracts

Please note that all code, files, forms, templates, or other materials provided or linked herein (the "Repo Contents") are provided by ChainLocker LLC strictly as-is under the MIT License; no guarantee, representation or warranty is being made, express or implied, as to the safety or correctness of any of the Repo Contents or any smart contracts or other software deployed from these files.

Any users, developers, or adapters of the Repo Contents or any deployments or instances thereof should proceed with caution and use at their own risk.


ChainLocker is a non-custodial, user-defined-and-deployed escrow deployment protocol. Each ChainLocker is a separate contract deployment, and is designed to only hold one type of asset per deployment.

Deployers may choose to create an 'openOffer' ChainLocker that is open to any counterparty, a 'refundable' ChainLocker which enables the counterparty to withdraw their 'deposit' if there has been no successful execution before the 'expirationTime', a ChainLocker with execution contigent on an oracle-fed data condition known as a 'ValueCondition', and more.

The locked assets are programmatically released provided all deployer-defined conditions are met when execute() is called. ChainLockers may be re-used so long as the expiration time has not been reached. If the necessary conditions are not met before the deployer-defined expiry, assets become withdrawable according to the deployer-defined deposit and refundability rules.

tl;dr:

(1) User calls deployChainLocker() with all applicable parameters to deploy a ChainLocker

(2) Buyer (or any address, if the ChainLocker is an open offer) deposits the applicable assets into the ChainLocker

(3) Buyer and Seller each call readytoExecute() when ready to complete the transaction

(4) Anyone may call execute(). If the total amount is in the ChainLocker, each party is ready to execute, the expiration time has not been met and any applicable Value Condition check is satisfied, the ChainLocker executes and the total amount is transferred to Seller

(5) If the expiration time has been reached when execute() is called, the refundability rules apply and each party must call withdraw() to withdraw their respective amounts. If Seller decided to reject a depositor/Buyer by calling rejectDepositor(), that address must call withdraw() to withdraw their deposited amount


ChainLockerFactory.sol

Factory contract for ChainLocker deployments (TokenLocker or EthLocker) based upon the various parameters passed to deployChainLocker(). For deployments with a non-zero ValueCondition, the execution of the ChainLocker pre-expiry is reliant upon the accurate operation and security of the applicable dataFeedProxy contract (specifically its read() function) so users are advised to carefully verify and monitor such contract. The deployer of a ChainLocker does not need to be buyer or seller, and each of buyer and seller can replace their own address after deployment. The parameters for a ChainLocker deployment by calling deployChainLocker() are:

  • _refundable: Boolean of whether the deposit amount is refundable to buyer at the expirationTime
  • _openOffer: Boolean of whether the ChainLocker is open to any buyer address (true) or only the designated buyer address
  • _valueCondition: enum (uint8) of external data value condition for execution. Note that a ChainLocker user/deployer can arrange for a bespoke proxy contract that conforms to the IProxy interface (i.e. has a read() view function that returns a timestamp and int224 value), the returned ‘value’ can be used for any type of non-negative numerical data, and the “Both” ValueCondition can be leveraged to require a response between a minimum and maximum value or an exact response (by submitting the same value for _minimumValue and _maximumValue). Enum values are as follows:
    • 0 ('None'): no value contingency to ChainLocker execution; _maximumValue, _minimumValue and _dataFeedProxyAddress params are ignored;
    • 1 ('LessThanOrEqual'): the value returned from _dataFeedProxyAddress must be <= _maximumValue when calling execute(); _minimumValue param is ignored;
    • 2 ('GreaterThanOrEqual'): the value returned from _dataFeedProxyAddress must be >= _minimumValue when calling execute(); _maximumValue param is ignored
    • 3 ('Both'): the value returned from _dataFeedProxyAddress must be both <= _maximumValue and >= _minimumValue when calling execute()
  • _minimumValue: int224 for ValueCondition check in execute() as set forth above, if applicable;
  • _maximumValue: int224 for ValueCondition check in execute() as set forth above, if applicable;
  • _deposit: uint256 amount of deposit, which must be <= _totalAmount and will be refunded to buyer at expiry if refundable == true;
  • _totalAmount: uint256 total amount of wei or tokens to be locked in ChainLocker;
  • _expirationTime: uint256 Unix time of ChainLocker’s expiry;
  • _seller: address payable of the seller (ultimate recipient of totalAmount should the ChainLocker execute). Replaceable by seller post-deployment by calling updateSeller with the new address payable;
  • _buyer: address payable of the buyer (depositor of deposit/totalAmount and recipient of deposit’s return at expiry if refundable == true). Replaceable by buyer post-deployment by calling updateBuyer with the new address payable. Ignored if openOffer == true;
  • _tokenContract: address for the ERC20-compliant token contract used when deploying a TokenLocker; if deploying an EthLocker, pass address(0);
  • _dataFeedProxy: address which will be called if _valueCondition > 0 in execute which must correctly implement the read() function as defined in the IProxy interface. Intended to utilize API3’s dAPIs.

The ChainLockerFactory contract contains an inactive fee switch for deployments, but may be used in the future to automatically top-up data feed contracts for sustainability. Instead of using deployChainLocker(), users may bypass this contract entirely and instead directly deploy an EthLocker or TokenLocker.


EthLocker.sol

Non-custodial escrow smart contract using the native gas token as locked asset, and initiated with the following immutable parameters (supplied by the ChainLockerFactory’s deployChainLocker() function or directly in the EthLocker constructor()):

  • _refundable: Boolean of whether the deposit amount is refundable to buyer at the expirationTime
  • _openOffer: Boolean of whether the EthLocker is open to any depositing address (true) or only the designated buyer address
  • _valueCondition: enum (uint8) of external data value condition for execution. Enum values are as follows (the same as specified above for deployChainLocker() in ChainLockerFactory):
    • 0 ('None'): no value contingency to execution; _maximumValue, _minimumValue and _dataFeedProxyAddress params are ignored;
    • 1 ('LessThanOrEqual'): the value returned from _dataFeedProxyAddress must be <= _maximumValue when calling execute(); _minimumValue param is ignored;
    • 2 ('GreaterThanOrEqual'): the value returned from _dataFeedProxyAddress must be >= _minimumValue when calling execute(); _maximumValue param is ignored
    • 3 ('Both'): the value returned from _dataFeedProxyAddress must be both <= _maximumValue and >= _minimumValue when calling execute()
  • _minimumValue: int224 for ValueCondition check in execute() as set forth above, if applicable;
  • _maximumValue: int224 for ValueCondition check in execute() as set forth above, if applicable;
  • _deposit: uint256 amount in wei of deposit, which must be <= _totalAmount and will be refunded to buyer at expiry if refundable == true;
  • _totalAmount: uint256 total amount in wei to be locked;
  • _expirationTime: uint256 Unix time of expiry;
  • _seller: address payable of the seller (ultimate recipient of totalAmount should the EthLocker execute). Replaceable by seller post-deployment by calling updateSeller with the new address payable;
  • _buyer: address payable of the buyer (depositor of deposit/totalAmount and recipient of deposit’s return at expiry if refundable == true). Replaceable by buyer post-deployment by calling updateBuyer with the new address payable. Ignored if openOffer == true;
  • _dataFeedProxy: address which will be called if _valueCondition > 0 in execute which must correctly implement the read() function as defined in the IProxy interface. Intended to utilize API3’s dAPIs.

Buyer deposits into an EthLocker by sending the proper amount of wei directly to the contract address, invoking its receive() function. For open offers, the entire totalAmount must be deposited to become the buyer. However, even if the EthLocker is not an open offer, any address may deposit-- this allows for greater composability and flexibility in usecases not available in TokenLockers due to the latter's necessary approve/permit + deposit pattern.

The receive() function has a condition check to ensure no more than totalAmount - pendingWithdraw can be sent to the EthLocker.

If seller wishes to reject a depositor or buyer, seller may call rejectDepositor(), supplying the applicable address to be rejected (the applicable amountWithdrawable mapping will update for the amount the rejected address had previously deposited into the EthLocker, allowing such rejected address to withdraw their corresponding amount).

The checkIfExpired() function may also be called by any address at any time, and if the expirationDate has been met, the amountWithdrawable mapping(s) will update according to the deployer-defined refundability rules.

Addresses with a nonzero amountWithdrawable mapped value can withdraw by calling withdraw().

When each of buyer and seller are ready to execute the EthLocker, they must call readyToExecute(). Following this, any address may call execute(), and if the totalAmount is held by the EthLocker, expirationTime has not yet been met, and (if applicable) the ValueCondition is satisfied, the EthLocker will execute and send the totalAmount to seller.


TokenLocker.sol

Non-custodial escrow smart contract with mirrored functionality as ‘EthLocker’, but using an ERC20-compliant token as locked asset and the ability to lock such tokens via EIP2612 ‘permit’ function (if applicable). Fee-on-transfer and rebasing tokens are not supported as an opinionated optimization in favor of immutable fixed amounts. Initiated with the following immutable parameters (supplied by the ChainLockerFactory’s deployChainLocker() function or directly in the TokenLocker constructor()):

  • _refundable: Boolean of whether the deposit amount is refundable to buyer at the expirationTime
  • _openOffer: Boolean of whether the TokenLocker is open to any depositing address (true) or only the designated buyer address
  • _valueCondition: enum (uint8) of external data value condition for execution. Enum values are as follows (the same as specified above for deployChainLocker() in ChainLockerFactory):
    • 0 ('None'): no value contingency to execution; _maximumValue, _minimumValue and _dataFeedProxyAddress params are ignored;
    • 1 ('LessThanOrEqual'): the value returned from _dataFeedProxyAddress must be <= _maximumValue when calling execute(); _minimumValue param is ignored;
    • 2 ('GreaterThanOrEqual'): the value returned from _dataFeedProxyAddress must be >= _minimumValue when calling execute(); _maximumValue param is ignored
    • 3 ('Both'): the value returned from _dataFeedProxyAddress must be both <= _maximumValue and >= _minimumValue when calling execute()
  • _minimumValue: int224 for ValueCondition check in execute() as set forth above, if applicable;
  • _maximumValue: int224 for ValueCondition check in execute() as set forth above, if applicable;
  • _deposit: uint256 token amount of deposit, which must be <= _totalAmount and will be refunded to buyer at expiry if refundable == true;
  • _totalAmount: uint256 total amount of tokens to be locked;
  • _expirationTime: uint256 Unix time of expiry;
  • _seller: address payable of the seller (ultimate recipient of totalAmount should the TokenLocker execute). Replaceable by seller post-deployment by calling updateSeller with the new address payable;
  • _buyer: address payable of the buyer (depositor of deposit/totalAmount and recipient of deposit’s return at expiry if refundable == true). Replaceable by buyer post-deployment by calling updateBuyer with the new address payable. Ignored if openOffer == true;
  • _tokenContract: address for the ERC20-compliant token contract to be locked. Must be EIP2612-compliant for the buyer to use depositTokensWithPermit();
  • _dataFeedProxy: address which will be called if _valueCondition > 0 in execute which must correctly implement the read() function as defined in the IProxy interface. Intended to utilize API3’s dAPIs.

Buyer deposits into an TokenLocker via either (A) depositTokensWithPermit() (if the applicable token contract has an EIP2612 permit() function) supplying the address that is transferring such tokens, the amount of tokens, and remainder of the permit() signature parameters (deadline, v, r, s), or (B) calling the token's approve() function supplying the address of the TokenLocker and the amount of tokens to be deposited, then calling depositTokens() in the TokenLocker supplying the address that approved the TokenLocker accordingly and is transferring such tokens, and the amount of tokens, in order to send the proper amount of tokens to the TokenLocker. For open offers, the entire totalAmount must be deposited to become the buyer.

While the deposit functions have condition checks to ensure no more than totalAmount - pendingWithdraw is deposited in the TokenLocker via such functions, users must be careful not to send any tokens directly to the TokenLocker's contract address as they will not be recoverable. Unlike EthLockers, one cannot properly deposit to a TokenLocker by simply transferring to the address; the proper functions must be called.

If seller wishes to reject a depositor or buyer, seller may call rejectDepositor(), supplying the applicable address to be rejected (the applicable amountWithdrawable mapping will update for the amount the rejected address had previously deposited into the TokenLocker, allowing such rejected address to withdraw their corresponding amount).

The checkIfExpired() function may also be called by any address at any time, and if the expirationDate has been met, the amountWithdrawable mapping(s) will update according to the deployer-defined refundability rules.

Addresses with a nonzero amountWithdrawable mapped value can withdraw by calling withdraw().

When each of buyer and seller are ready to execute the TokenLocker, they must call readyToExecute(). Following this, any address may call execute(), and if the totalAmount is held by the TokenLocker, expirationTime has not yet been met, and (if applicable) the ValueCondition is satisfied, the TokenLocker will execute and send the totalAmount to seller.


Receipt.sol

Informational contract which allows a user to receive an immutable receipt of a transaction's value in USD (if the asset has an active and funded dataFeedProxy supplied to the tokenToProxy mapping) by calling printReceipt(), supplying:

  • _token: address for the ERC20-compliant token contract (or address(0) for ETH/native gas token)
  • _tokenAmount: uint256 total amount of wei or tokens
  • _decimals: uint256 decimals of '_token' for USD value calculation (18 for wei)

Callers are also provided a paymentId if they print a receipt, which can be used to check their applicable USD value at any time by calling paymentIdToUsdValue(), supplying their paymentId as a parameter. Data feed proxy contracts may be added, updated, and removed only by this contract’s admin.

As this contract is purely informational, optional, and never a contingency for a ChainLocker’s operation, ChainLockers remain entirely ownerless and non-custodial.