Skip to content

Commit

Permalink
Delegate and SwapERC20 Finalizations (#1322)
Browse files Browse the repository at this point in the history
* Use of hashTypeData for code clarity and check chain ID (#1318)

* publish libraries 4.2.1-beta.0

* Removed redundant chain ID checks

* Removed unused error and reduced max_error count by one

* Removed unused error from swap and reduced MAX_ERROR by 1

---------

Co-authored-by: don mosites <mosites@gmail.com>

* naming, comments, adjustments

* Updated NatSpec

---------

Co-authored-by: Smartcontrart <100962328+smartcontrart@users.noreply.github.com>
Co-authored-by: Smart Contrart <smartcontrart@protonmail.com>
  • Loading branch information
3 people authored Aug 28, 2024
1 parent fc557e7 commit cf58ace
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 80 deletions.
93 changes: 60 additions & 33 deletions source/delegate/contracts/Delegate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ import { Ownable } from "solady/src/auth/Ownable.sol";
import { SafeTransferLib } from "solady/src/utils/SafeTransferLib.sol";

/**
* @title Delegate: Deployable Trading Rules for the AirSwap Network
* @notice Supports fungible tokens (ERC-20)
* @title AirSwap: Delegated On-chain Trading Rules
* @notice Supports ERC-20 tokens
* @dev inherits IDelegate, Ownable and uses SafeTransferLib
*/
contract Delegate is IDelegate, Ownable {
// The Swap contract to be used to settle trades
// The SwapERC20 contract to be used to execute orders
ISwapERC20 public swapERC20;

// Mapping of sender to senderToken to to signerToken to Rule
mapping(address => mapping(address => mapping(address => Rule))) public rules;

// Mapping of signer to authorized signatory
// Mapping of sender to authorized manager
mapping(address => address) public authorized;

/**
Expand All @@ -32,68 +32,91 @@ contract Delegate is IDelegate, Ownable {
}

/**
* @notice Set a Trading Rule
* @param _senderToken address Address of an ERC-20 token the consumer would send
* @param _senderRuleAmount uint256 Maximum amount of ERC-20 token the sender wants to swap
* @param _signerToken address Address of an ERC-20 token the delegate would recieve
* @param _signerAmount uint256 Minimum amount of ERC-20 token the delegate would recieve
* @notice Set a Rule
* @param _senderWallet The address of the sender's wallet
* @param _senderToken address ERC-20 token the sender would transfer
* @param _senderAmount uint256 Maximum sender amount for the rule
* @param _signerToken address ERC-20 token the signer would transfer
* @param _signerAmount uint256 Maximum signer amount for the rule
* @param _expiry uint256 Expiry in seconds since 1 January 1970
*/
function setRule(
address _senderWallet,
address _senderToken,
uint256 _senderRuleAmount,
uint256 _senderAmount,
address _signerToken,
uint256 _signerAmount,
uint256 _ruleExpiry
uint256 _expiry
) external {
if (authorized[_senderWallet] != address(0)) {
if (authorized[_senderWallet] != msg.sender) revert SenderInvalid();
// If an authorized manager is set, message sender must be the manager
if (msg.sender != authorized[_senderWallet]) revert SenderInvalid();
} else {
if (_senderWallet != msg.sender) revert SenderInvalid();
// Otherwise message sender must be the sender wallet
if (msg.sender != _senderWallet) revert SenderInvalid();
}

// Set the rule. Overwrites an existing rule.
rules[_senderWallet][_senderToken][_signerToken] = Rule(
_senderWallet,
_senderToken,
_senderRuleAmount,
_senderAmount,
0,
_signerToken,
_signerAmount,
_ruleExpiry
_expiry
);

emit SetRule(
_senderWallet,
_senderToken,
_senderRuleAmount,
0,
_senderAmount,
_signerToken,
_signerAmount,
_ruleExpiry
_expiry
);
}

/**
* @notice Unset a Trading Rule
* @dev only callable by the owner of the contract, removes from a mapping
* @param _senderToken address Address of an ERC-20 token the sender would send
* @param _signerToken address Address of an ERC-20 token the delegate would receive
* @notice Unset rule
* @param _senderWallet The address of the sender's wallet
* @param _senderToken address sender token of the rule
* @param _signerToken address signer token of the rule
*/
function unsetRule(
address _senderWallet,
address _senderToken,
address _signerToken
) external {
if (authorized[_senderWallet] != address(0)) {
if (authorized[_senderWallet] != msg.sender) revert SenderInvalid();
// If an authorized manager is set, the message sender must be the manager
if (msg.sender != authorized[_senderWallet]) revert SenderInvalid();
} else {
if (_senderWallet != msg.sender) revert SenderInvalid();
// Otherwise the message sender must be the sender wallet
if (msg.sender != _senderWallet) revert SenderInvalid();
}

// Delete the rule
delete rules[_senderWallet][_senderToken][_signerToken];

emit UnsetRule(_senderWallet, _senderToken, _signerToken);
}

/**
* @notice Atomic ERC20 Swap
* @notice forwards to the underlying SwapERC20 contract
* @param _senderWallet address Wallet to receive sender proceeds
* @param _nonce uint256 Unique and should be sequential
* @param _expiry uint256 Expiry in seconds since 1 January 1970
* @param _signerWallet address Wallet of the signer
* @param _signerToken address ERC20 token transferred from the signer
* @param _signerAmount uint256 Amount transferred from the signer
* @param _senderToken address ERC20 token transferred from the sender
* @param _senderAmount uint256 Amount transferred from the sender
* @param _v uint8 "v" value of the ECDSA signature
* @param _r bytes32 "r" value of the ECDSA signature
* @param _s bytes32 "s" value of the ECDSA signature
*/
function swap(
address _senderWallet,
uint256 _nonce,
Expand All @@ -108,29 +131,31 @@ contract Delegate is IDelegate, Ownable {
bytes32 _s
) external {
Rule storage rule = rules[_senderWallet][_senderToken][_signerToken];

if (
_signerAmount <
(rule.signerAmount * (rule.senderRuleAmount - rule.senderFilledAmount)) /
rule.senderRuleAmount
(rule.signerAmount * (rule.senderAmount - rule.senderFilledAmount)) /
rule.senderAmount
) {
revert InvalidSignerAmount();
revert SignerAmountInvalid();
}
if (rule.ruleExpiry < block.timestamp) revert RuleExpired();
if (rule.expiry < block.timestamp) revert RuleExpired();

if (_senderAmount > (rule.senderRuleAmount - rule.senderFilledAmount)) {
revert InvalidSenderAmount();
if (_senderAmount > (rule.senderAmount - rule.senderFilledAmount)) {
revert SenderAmountInvalid();
}

// Transfer the sender token to this contract
SafeTransferLib.safeTransferFrom(
_senderToken,
_senderWallet,
address(this),
_senderAmount
);

// Approve the SwapERC20 contract to transfer the sender token
ERC20(_senderToken).approve(address(swapERC20), _senderAmount);

// Execute the swap
swapERC20.swapLight(
_nonce,
_expiry,
Expand All @@ -144,16 +169,18 @@ contract Delegate is IDelegate, Ownable {
_s
);

// Transfer the signer token to the sender wallet
SafeTransferLib.safeTransfer(_signerToken, _senderWallet, _signerAmount);

// Update the filled amount
rules[_senderWallet][_senderToken][_signerToken]
.senderFilledAmount += _senderAmount;
emit DelegateSwap(_nonce, _signerWallet);
}

/**
* @notice Authorize a wallet to manage rules
* @param _manager address Wallet of the signatory to authorize
* @param _manager address Wallet of the manager to authorize
* @dev Emits an Authorize event
*/
function authorize(address _manager) external {
Expand All @@ -177,7 +204,7 @@ contract Delegate is IDelegate, Ownable {
* @param _swapERC20 ISwapERC20 The SwapERC20 contract
*/
function setSwapERC20Contract(ISwapERC20 _swapERC20) external onlyOwner {
if (address(_swapERC20) == address(0)) revert InvalidAddress();
if (address(_swapERC20) == address(0)) revert AddressInvalid();
swapERC20 = _swapERC20;
}
}
31 changes: 15 additions & 16 deletions source/delegate/contracts/interfaces/IDelegate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,46 +6,45 @@ pragma solidity 0.8.23;

interface IDelegate {
struct Rule {
address sender;
address senderWallet;
address senderToken;
uint256 senderRuleAmount;
uint256 senderAmount;
uint256 senderFilledAmount;
address signerToken;
uint256 signerAmount;
uint256 ruleExpiry;
uint256 expiry;
}

error RuleExpired();
error InvalidAddress();
error InvalidSenderAmount();
error InvalidSignerAmount();
error ManagerInvalid();
error SenderInvalid();
error TransferFromFailed();

event Authorize(address signatory, address signer);
event DelegateSwap(uint256 nonce, address signerWallet);
event Revoke(address tmp, address signer);

event SetRule(
address senderWallet,
address senderToken,
uint256 senderRuleAmount,
uint256 senderFilledAmount,
uint256 senderAmount,
address signerToken,
uint256 signerAmount,
uint256 ruleExpiry
uint256 expiry
);

event UnsetRule(address signer, address signerToken, address senderToken);

error AddressInvalid();
error RuleExpired();
error SenderAmountInvalid();
error SignerAmountInvalid();
error SenderInvalid();
error ManagerInvalid();
error TransferFromFailed();

function setRule(
address sender,
address senderToken,
uint256 senderRuleAmount,
uint256 senderAmount,
address signerToken,
uint256 signerAmount,
uint256 ruleExpiry
uint256 expiry
) external;

function swap(
Expand Down
1 change: 1 addition & 0 deletions source/delegate/deploys-blocks.js.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module '@airswap/delegate/deploys-blocks.js'
1 change: 0 additions & 1 deletion source/delegate/deploys.js.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
declare module '@airswap/delegate/deploys.js'
declare module '@airswap/delegate/deploys-blocks.js'
6 changes: 2 additions & 4 deletions source/delegate/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@airswap/delegate",
"version": "4.3.0-beta.0",
"description": "AirSwap: Delegate On-chain Trading Rules",
"description": "AirSwap: Delegated On-chain Trading Rules",
"license": "MIT",
"repository": {
"type": "git",
Expand All @@ -22,10 +22,8 @@
"test": "hardhat test",
"test:ci": "hardhat test",
"deploy": "hardhat run ./scripts/deploy.js",
"verify": "hardhat run ./scripts/verify.js",
"owners": "hardhat run ./scripts/owner.js",
"migrate": "hardhat run ./scripts/migrate.js",
"balances": "hardhat run ./scripts/balances.js"
"verify": "hardhat run ./scripts/verify.js"
},
"devDependencies": {
"@airswap/utils": "4.3.3",
Expand Down
10 changes: 4 additions & 6 deletions source/delegate/test/Delegate.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ describe('Delegate Unit', () => {
sender.address,
senderToken.address,
DEFAULT_SENDER_AMOUNT,
0,
signerToken.address,
DEFAULT_SIGNER_AMOUNT,
RULE_EXPIRY
Expand Down Expand Up @@ -205,7 +204,6 @@ describe('Delegate Unit', () => {
sender.address,
senderToken.address,
DEFAULT_SENDER_AMOUNT,
0,
signerToken.address,
DEFAULT_SIGNER_AMOUNT,
RULE_EXPIRY
Expand Down Expand Up @@ -252,7 +250,7 @@ describe('Delegate Unit', () => {
signerToken.address
)

expect(rule.senderRuleAmount.toString()).to.equal(DEFAULT_SENDER_AMOUNT)
expect(rule.senderAmount.toString()).to.equal(DEFAULT_SENDER_AMOUNT)
})

it('unsetting a Rule updates the rule balance', async () => {
Expand Down Expand Up @@ -283,7 +281,7 @@ describe('Delegate Unit', () => {
signerToken.address
)

expect(rule.senderRuleAmount.toString()).to.equal('0')
expect(rule.senderAmount.toString()).to.equal('0')
})
})

Expand Down Expand Up @@ -413,7 +411,7 @@ describe('Delegate Unit', () => {

await expect(
delegate.connect(signer).swap(sender.address, ...order)
).to.be.revertedWith('InvalidSenderAmount')
).to.be.revertedWith('SenderAmountInvalid')
})

it('fails to swap with insufficient signer amount on Rule', async () => {
Expand Down Expand Up @@ -453,7 +451,7 @@ describe('Delegate Unit', () => {

await expect(
delegate.connect(signer).swap(sender.address, ...order)
).to.be.revertedWith('InvalidSignerAmount')
).to.be.revertedWith('SignerAmountInvalid')
})

it('fails to swap with a rule expired', async () => {
Expand Down
14 changes: 7 additions & 7 deletions source/swap-erc20/contracts/SwapERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ contract SwapERC20 is ISwapERC20, Ownable, EIP712 {
uint256 _bonusScale,
uint256 _bonusMax
) {
if (_protocolFee >= FEE_DIVISOR) revert InvalidFee();
if (_protocolFeeLight >= FEE_DIVISOR) revert InvalidFeeLight();
if (_protocolFeeWallet == address(0)) revert InvalidFeeWallet();
if (_protocolFee >= FEE_DIVISOR) revert ProtocolFeeInvalid();
if (_protocolFeeLight >= FEE_DIVISOR) revert ProtocolFeeLightInvalid();
if (_protocolFeeWallet == address(0)) revert ProtocolFeeWalletInvalid();
if (_bonusMax > MAX_MAX) revert MaxTooHigh();
if (_bonusScale > MAX_SCALE) revert ScaleTooHigh();

Expand Down Expand Up @@ -324,7 +324,7 @@ contract SwapERC20 is ISwapERC20, Ownable, EIP712 {
*/
function setProtocolFee(uint256 _protocolFee) external onlyOwner {
// Ensure the fee is less than divisor
if (_protocolFee >= FEE_DIVISOR) revert InvalidFee();
if (_protocolFee >= FEE_DIVISOR) revert ProtocolFeeInvalid();
protocolFee = _protocolFee;
emit SetProtocolFee(_protocolFee);
}
Expand All @@ -335,7 +335,7 @@ contract SwapERC20 is ISwapERC20, Ownable, EIP712 {
*/
function setProtocolFeeLight(uint256 _protocolFeeLight) external onlyOwner {
// Ensure the fee is less than divisor
if (_protocolFeeLight >= FEE_DIVISOR) revert InvalidFeeLight();
if (_protocolFeeLight >= FEE_DIVISOR) revert ProtocolFeeLightInvalid();
protocolFeeLight = _protocolFeeLight;
emit SetProtocolFeeLight(_protocolFeeLight);
}
Expand All @@ -346,7 +346,7 @@ contract SwapERC20 is ISwapERC20, Ownable, EIP712 {
*/
function setProtocolFeeWallet(address _protocolFeeWallet) external onlyOwner {
// Ensure the new fee wallet is not null
if (_protocolFeeWallet == address(0)) revert InvalidFeeWallet();
if (_protocolFeeWallet == address(0)) revert ProtocolFeeWalletInvalid();
protocolFeeWallet = _protocolFeeWallet;
emit SetProtocolFeeWallet(_protocolFeeWallet);
}
Expand Down Expand Up @@ -379,7 +379,7 @@ contract SwapERC20 is ISwapERC20, Ownable, EIP712 {
*/
function setStaking(address _stakingToken) external onlyOwner {
// Ensure the new staking token is not null
if (_stakingToken == address(0)) revert InvalidStaking();
if (_stakingToken == address(0)) revert StakingInvalid();
stakingToken = _stakingToken;
emit SetStaking(_stakingToken);
}
Expand Down
Loading

0 comments on commit cf58ace

Please sign in to comment.